package com.uinnova.product.eam.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.binary.core.exception.BinaryException;
import com.binary.core.io.Resource;
import com.binary.core.io.support.ByteArrayResource;
import com.binary.core.util.BinaryUtils;
import com.binary.framework.exception.ServiceException;
import com.binary.jdbc.Page;
import com.google.common.collect.Lists;
import com.uinnova.product.eam.base.util.EamUtil;
import com.uinnova.product.eam.model.vo.ESRltSearchBeanVO;
import com.uinnova.product.eam.service.es.*;
import com.uinnova.product.vmdb.comm.model.ci.CCcCiClass;
import com.uinnova.product.vmdb.comm.model.ci.CcCi;
import com.uinnova.product.vmdb.comm.model.ci.CcCiAttrDef;
import com.uinnova.product.vmdb.provider.ci.bean.CcCiClassInfo;
import com.uinnova.product.vmdb.provider.rlt.bean.CcCiRltInfo;
import com.uino.bean.cmdb.base.*;
import com.uino.bean.cmdb.business.*;
import com.uino.bean.cmdb.enums.AttrNameKeyEnum;
import com.uino.bean.cmdb.query.ESAttrAggBean;
import com.uino.bean.cmdb.query.ESRltSearchBean;
import com.uino.dao.cmdb.ESCIClassSvc;
import com.uino.dao.cmdb.ESCIRltInfoHistorySvc;
import com.uino.dao.cmdb.ESRltClassSvc;
import com.uino.dao.util.ESUtil;
import com.uino.service.cmdb.microservice.ICIRltSvc;
import com.uino.service.cmdb.microservice.impl.CIRltSvc;
import com.uino.service.util.FileUtil;
import com.uino.util.rsm.RsmUtils;
import com.uino.util.sys.CheckAttrUtil;
import com.uino.util.sys.SysUtil;
import com.uino.util.sys.ValidDtoUtil;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.TermsQueryBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.web.multipart.MultipartFile;

import java.io.*;
import java.math.BigDecimal;
import java.text.MessageFormat;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.stream.Collectors;

import static com.uino.util.sys.CheckAttrUtil.toStdMap;

@Service
public class IamsCIRltDesignSvc implements ICIRltSvc {

    private static final Logger log = LoggerFactory.getLogger(IamsCIRltDesignSvc.class);

    @Autowired
    private IamsESCIRltDesignSvc esCiRltSvc;

    @Autowired
    private IamsESCIDesignSvc esCiSvc;

    @Autowired
    private ESRltClassSvc esRltClassSvc;

    @Autowired
    private ESCIClassSvc esClassSvc;

    @Autowired
    private IamsESCmdbCommDesignSvc esCmdbCommSvc;

    @Autowired
    private IamsESCIRltInfoHistoryDesignSvc esCIRltInfoHistorySvc;

    @Autowired
    private RsmUtils rsmUtils;

    @Value("${http.resource.space}")
    private String urlPath;

    @Value("${local.resource.space}")
    private String localPath;

    @Autowired
    private IamsCIRltDesignNonComplianceDao iamsCIRltDesignNonComplianceDao;

    @Override
    public Long bindCiRlt(BindCiRltRequestDto bindCiRltRequestDto) {

        Long sCiId = bindCiRltRequestDto.getSourceCiId();
        Long tCiId = bindCiRltRequestDto.getTargetCiId();
        Long rltClassId = bindCiRltRequestDto.getRltClassId();
        Map<String, String> attrs = bindCiRltRequestDto.getAttrs();
        boolean repetitionError = bindCiRltRequestDto.isRepetitionError();

        // 1先验证参数合法性：s/t是否存在 s/t是否再同分类下 rltcls是否存在
        ESCIInfo sourceCi = esCiSvc.getById(sCiId);
        Assert.notNull(sourceCi, "源ci不存在");
        ESCIInfo targetCi = esCiSvc.getById(tCiId);
        ESCIClassInfo sourceCiClass = esClassSvc.getById(sourceCi.getClassId());
        List<String> sourceSearchFiles = getSearchFiles(sourceCiClass);
        Assert.notNull(sourceCi, "目标ci不存在");
        ESCIClassInfo targetCiClass = esClassSvc.getById(targetCi.getClassId());
        List<String> targetSearchFiles = getSearchFiles(targetCiClass);
        ESCIClassInfo rltCls = esRltClassSvc.getById(rltClassId);
        Assert.notNull(rltCls, "指定关系分类不存在");
        esCmdbCommSvc.validAttrs(rltCls.getCcAttrDefs(), attrs);
        // 2到了这里代表通过了所有正常验证，开始删除sCiId=》tCiId已存在关系，要建立sCiId=》tCiId则视为用户要重建关系
        ESCIRltInfo bindRlt = new ESCIRltInfo();
        bindRlt.setClassId(rltClassId);
        bindRlt.setAttrs(attrs);
        bindRlt.setRltCiInfo(sourceCi, targetCi,sourceSearchFiles,targetSearchFiles);
        bindRlt.setSourceCiSearchValues(sourceCiClass.getClassName()+"_"+bindRlt.getSourceCiSearchValues());
        bindRlt.setTargetCiSearchValues(targetCiClass.getClassName()+"_"+bindRlt.getTargetCiSearchValues());
        String uniqueCode = esCmdbCommSvc.getUniqueCode(rltCls.getCcAttrDefs(), bindRlt.getAttrs(), bindRlt.getCiCode());
        bindRlt.setUniqueCode(uniqueCode);
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        boolQuery.must(QueryBuilders.termQuery("uniqueCode.keyword", bindRlt.getUniqueCode()));
        List<ESCIRltInfo> query = esCiRltSvc.getListByQuery(boolQuery);
        if (!BinaryUtils.isEmpty(query)) {
            if (repetitionError) {
                Assert.isTrue(query.size() <= 0, "该关系已存在");
            }
            bindRlt.setId(query.get(0).getId());
        }
        // 关系数据与CI数据一致 添加 localVersion 与 publishVersion 字段 用作版本管理
        EamUtil.setVersionAttrs(bindRlt, 0L, 1L);
        Long id = esCiRltSvc.saveOrUpdate(bindRlt);
        bindRlt.setId(id);
        // 设计库关系历史库记录数据
        esCIRltInfoHistorySvc.saveCIRltHistoryByCIRlts(bindRlt);
        return id;
    }

    public void bindBatchCiRlt(List<ESCIRltInfo> rltList, String loginCode) {
        Optional<ESCIRltInfo> present = rltList.stream().filter(each -> BinaryUtils.isEmpty(each.getUniqueCode())).findAny();
        if(present.isPresent()){
            throw new BinaryException("存在uniqueCode为空");
        }
        Set<String> ciCodes = new HashSet<>();
        rltList.forEach(each -> {
            ciCodes.add(each.getSourceCiCode());
            ciCodes.add(each.getTargetCiCode());
        });
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        boolQuery.must(QueryBuilders.termsQuery("ciCode.keyword", ciCodes));
        List<ESCIInfo> list = esCiSvc.selectListByQuery(1, ciCodes.size(), boolQuery);
        List<String> exitsCiCodes = list.stream().map(ESCIInfo::getCiCode).distinct().collect(Collectors.toList());
        Map<String, ESCIInfo> ciCodeMap = list.stream().collect(Collectors.toMap(CcCi::getCiCode, each -> each, (key1, key2) -> key2));
        List<String> uniqueCodes = rltList.stream().map(ESCIRltInfo::getUniqueCode).distinct().collect(Collectors.toList());
        BoolQueryBuilder rltQuery = QueryBuilders.boolQuery();
        rltQuery.must(QueryBuilders.termsQuery("uniqueCode.keyword", uniqueCodes));
        List<ESCIRltInfo> exitsRltList = esCiRltSvc.getListByQuery(rltQuery);
        Map<String, ESCIRltInfo> unRltMap = exitsRltList.stream().collect(Collectors.toMap(ESCIRltInfo::getUniqueCode, each -> each, (key1, key2)->key2));
        List<ESCIRltInfo> needRecordHistoryList = new ArrayList<>();
        for (ESCIRltInfo each : new ArrayList<>(rltList)) {
            if(!exitsCiCodes.contains(each.getSourceCiCode()) || !exitsCiCodes.contains(each.getTargetCiCode())){
                rltList.remove(each);
            }
            ESCIRltInfo dbRlt = unRltMap.get(each.getUniqueCode());
            each.setOwnerCode(loginCode);
            each.setCreator(loginCode);
            each.setModifier(loginCode);
            each.setCreateTime(BinaryUtils.getNumberDateTime());
            each.setModifyTime(BinaryUtils.getNumberDateTime());
            ESCIInfo sourceCi = ciCodeMap.get(each.getSourceCiCode());
            ESCIInfo targetCi = ciCodeMap.get(each.getTargetCiCode());
            if(!BinaryUtils.isEmpty(sourceCi)){
                each.setSourceCiId(sourceCi.getId());
                each.setSourceCiCode(sourceCi.getCiCode());
            }
            if(!BinaryUtils.isEmpty(targetCi)){
                each.setTargetCiId(targetCi.getId());
                each.setTargetCiCode(targetCi.getCiCode());
            }
            if(!BinaryUtils.isEmpty(dbRlt)){
                each.setId(dbRlt.getId());
                each.setCreateTime(dbRlt.getCreateTime());
                each.setCreator(dbRlt.getCreator());
                // 表示更新操作 判断attrs是否修改 版本号+1 初始值为1
                if (!CheckAttrUtil.checkAttrMapEqual(toStdMap(each.getAttrs()), dbRlt.getAttrs(), false)) {
                    EamUtil.setVersionAttrs(each, 0L, BinaryUtils.isEmpty(dbRlt.getPublicVersion()) ? 1L : dbRlt.getPublicVersion() + 1);
                    needRecordHistoryList.add(each);
                } else {
                    // 操作为修改数据
                    EamUtil.setVersionAttrs(each,0L, BinaryUtils.isEmpty(dbRlt.getPublicVersion()) ? 1L : dbRlt.getPublicVersion());
                }
            }else{
                each.setId(ESUtil.getUUID());
                // 表新增
                EamUtil.setVersionAttrs(each, 0L, 1L);
                needRecordHistoryList.add(each);
            }
        }
        esCiRltSvc.saveOrUpdateBatch(rltList);
        // 有变化的数据记录到历史库中
        esCIRltInfoHistorySvc.saveCIRltHistoryByCIRlts(needRecordHistoryList);
    }


    @Override
    public ImportResultMessage bindCiRlts(Long domainId, Set<BindCiRltRequestDto> bindRltDtos) {
        return null;
    }

    @Override
    public ImportResultMessage bindCiRlts(Long domainId, Set<BindCiRltRequestDto> bindRltDtos, boolean repeat) {
        return null;
    }

    @Override
    public Integer delRltByCiId(Long ciId) {
        QueryBuilder delQuery = QueryBuilders.multiMatchQuery(ciId, "sourceCiId", "targetCiId");
        esCIRltInfoHistorySvc.saveCIRltHistoryByRltQuery(delQuery, ESCIRltInfoHistorySvc.ActionType.DELETE);
        Integer res = esCiRltSvc.deleteByQuery(delQuery, true);
        esCIRltInfoHistorySvc.deleteByQuery(delQuery, true);
        return res;
    }

    @Override
    public Integer delRltByCiIds(Collection<Long> ciIds) {
        // TODO Auto-generated method stub
        QueryBuilder delQuery = QueryBuilders.boolQuery().should(QueryBuilders.termsQuery("sourceCiId", ciIds))
                .should(QueryBuilders.termsQuery("targetCiId", ciIds));
        esCIRltInfoHistorySvc.saveCIRltHistoryByRltQuery(delQuery, ESCIRltInfoHistorySvc.ActionType.DELETE);
        Integer res = esCiRltSvc.deleteByQuery(delQuery, true);
        // esCIRltInfoHistorySvc.deleteByQuery(delQuery, true);
        return res;
    }

    @Override
    public Integer delRltByCiClassId(Long classId) {
        QueryBuilder query = QueryBuilders.multiMatchQuery(classId, "sourceClassId", "targetClassId");
        esCIRltInfoHistorySvc.saveCIRltHistoryByRltQuery(query, ESCIRltInfoHistorySvc.ActionType.DELETE);
        Integer res = esCiRltSvc.deleteByQuery(query, true);
        // esCIRltInfoHistorySvc.deleteByQuery(query, true);
        return res;
    }

    @Override
    public Integer delRlts(Collection<Long> rltClsIds, Collection<Long> sourceCIClsIds,
                           Collection<Long> targetCIClsIds) {
        Assert.isTrue(
                (rltClsIds != null && !rltClsIds.isEmpty()) || (sourceCIClsIds != null && !sourceCIClsIds.isEmpty())
                        || (targetCIClsIds != null && !targetCIClsIds.isEmpty()),
                "request del param don't empty");
        BoolQueryBuilder query = QueryBuilders.boolQuery();
        if (rltClsIds != null && !rltClsIds.isEmpty()) {
            query.must(QueryBuilders.termsQuery("classId", rltClsIds));
        }
        if (sourceCIClsIds != null && !sourceCIClsIds.isEmpty()) {
            query.must(QueryBuilders.termsQuery("sourceClassId", sourceCIClsIds));
        }
        if (targetCIClsIds != null && !targetCIClsIds.isEmpty()) {
            query.must(QueryBuilders.termsQuery("targetClassId", targetCIClsIds));
        }
        esCIRltInfoHistorySvc.saveCIRltHistoryByRltQuery(query, ESCIRltInfoHistorySvc.ActionType.DELETE);
        Integer res = esCiRltSvc.deleteByQuery(query, true);
        // esCIRltInfoHistorySvc.deleteByQuery(query, true);
        return res;
    }

    @Override
    public Long updateCiRltAttr(Long ciRltId, Map<String, String> attrs) {
        ESCIRltInfo ciRlt = esCiRltSvc.getById(ciRltId);
        Map<String, String> dbAttrs = ciRlt.getAttrs();
        Assert.notNull(ciRlt, "指定关系不存在");
        ESCIClassInfo rltCls = esRltClassSvc.getById(ciRlt.getClassId());
        Assert.notNull(rltCls, "指定关系所属分类不存在");
        esCmdbCommSvc.validAttrs(rltCls.getCcAttrDefs(), attrs);
        ciRlt.setAttrs(attrs);
        String uniqueCode = esCmdbCommSvc.getUniqueCode(rltCls.getCcAttrDefs(), ciRlt.getAttrs(), ciRlt.getCiCode());
        ciRlt.setUniqueCode(uniqueCode);
        BoolQueryBuilder validQuery = QueryBuilders.boolQuery();
        validQuery.must(QueryBuilders.termQuery("uniqueCode.keyword", ciRlt.getUniqueCode()));
        validQuery.mustNot(QueryBuilders.termQuery("id", ciRltId));
        long countValid = esCiRltSvc.countByCondition(validQuery);
        Assert.isTrue(countValid <= 0L, "关系信息重复");
        Long res = null;
        if (!CheckAttrUtil.checkAttrMapEqual(dbAttrs, toStdMap(attrs), false)) {
            EamUtil.setVersionAttrs(ciRlt, 0L, BinaryUtils.isEmpty(ciRlt.getPublicVersion()) ? 1L : ciRlt.getPublicVersion() + 1);
            res = esCiRltSvc.saveOrUpdate(ciRlt);
            esCIRltInfoHistorySvc.saveCIRltHistoryByCIRlts(ciRlt);
        } else {
            Boolean flag = BinaryUtils.isEmpty(ciRlt.getPublicVersion());
            EamUtil.setVersionAttrs(ciRlt,0L, BinaryUtils.isEmpty(ciRlt.getPublicVersion()) ? 1L : ciRlt.getPublicVersion());
            res = esCiRltSvc.saveOrUpdate(ciRlt);
            // 历史数据没有版本号字段 仅在第一次更新的时候添加到历史库
            if (flag) {
                esCIRltInfoHistorySvc.saveCIRltHistoryByCIRlts(ciRlt);
            }
        }
        return res;
    }

    public void updateCiRltAttrBatch(Map<Long, Map<String, String>> attrMap) {
        if (CollectionUtils.isEmpty(attrMap)) {
            throw new BinaryException("ci关系信息不能为空");
        }
        Set<Long> ciRltIds = attrMap.keySet();
        TermsQueryBuilder ciRltQuery = QueryBuilders.termsQuery("id", ciRltIds);
        List<ESCIRltInfo> ciRlts = esCiRltSvc.getListByQueryScroll(ciRltQuery);
        if (CollectionUtils.isEmpty(ciRlts) || ciRlts.size() != ciRltIds.size()) {
            throw new BinaryException("关系不存在");
        }
        Set<Long> rltClsIds = ciRlts.stream().map(ESCIRltInfo::getClassId).filter(Objects::nonNull).collect(Collectors.toSet());
        if (CollectionUtils.isEmpty(rltClsIds)) {
            throw new BinaryException("指定关系所属分类不存在");
        }
        TermsQueryBuilder ciRltClsQuery = QueryBuilders.termsQuery("id", rltClsIds);
        List<ESCIClassInfo> rltClasses = esRltClassSvc.getListByQueryScroll(ciRltClsQuery);
        if (CollectionUtils.isEmpty(rltClasses) || rltClasses.size() != rltClsIds.size()) {
            throw new BinaryException("指定关系所属分类不存在");
        }
        Map<Long, ESCIClassInfo> rltClsMap = rltClasses.stream().collect(Collectors.toMap(ESCIClassInfo::getId, Function.identity()));
        Map<Long, String> uniqueCodeMap = new HashMap<>();
        for (ESCIRltInfo ciRlt : ciRlts) {
            ESCIClassInfo rltCls = rltClsMap.get(ciRlt.getClassId());
            String uniqueCode = esCmdbCommSvc.getUniqueCode(rltCls.getCcAttrDefs(), ciRlt.getAttrs(), ciRlt.getCiCode());
            uniqueCodeMap.put(ciRlt.getId(), uniqueCode);
        }
        BoolQueryBuilder validQuery = QueryBuilders.boolQuery();
        validQuery.must(QueryBuilders.termsQuery("uniqueCode.keyword", new HashSet<>(uniqueCodeMap.values())));
        List<ESCIRltInfo> sameUniqueCodeCiRlts = esCiRltSvc.getListByQueryScroll(validQuery);
        Map<String, Set<Long>> sameUniqueCodeCiRltMap = new HashMap<>();
        if (!CollectionUtils.isEmpty(sameUniqueCodeCiRlts)) {
            for (ESCIRltInfo sameUniqueCodeCiRlt : sameUniqueCodeCiRlts) {
                Set<Long> ids = sameUniqueCodeCiRltMap.getOrDefault(sameUniqueCodeCiRlt.getUniqueCode(), new HashSet<>());
                ids.add(sameUniqueCodeCiRlt.getId());
                sameUniqueCodeCiRltMap.put(sameUniqueCodeCiRlt.getUniqueCode(), ids);
            }
        }
        List<ESCIRltInfo> saveRltRecords = new ArrayList<>();
        List<ESCIRltInfo> saveRltHisRecords = new ArrayList<>();
        for (ESCIRltInfo ciRlt : ciRlts) {
            Long ciRltId = ciRlt.getId();
            Map<String, String> dbAttrs = ciRlt.getAttrs();
            Map<String, String> attrs = attrMap.get(ciRltId);
            ESCIClassInfo rltCls = rltClsMap.get(ciRlt.getClassId());
            esCmdbCommSvc.validAttrs(rltCls.getCcAttrDefs(), attrs);
            ciRlt.setAttrs(attrs);
            String uniqueCode = uniqueCodeMap.get(ciRltId);
            ciRlt.setUniqueCode(uniqueCode);
            if (sameUniqueCodeCiRltMap.containsKey(uniqueCode)) {
                for (Long sameUniqueCodeRltId : sameUniqueCodeCiRltMap.get(uniqueCode)) {
                    if (!sameUniqueCodeRltId.equals(ciRltId)) {
                        throw new BinaryException("关系信息重复");
                    }
                }
            }
            if (!CheckAttrUtil.checkAttrMapEqual(dbAttrs, toStdMap(attrs), false)) {
                EamUtil.setVersionAttrs(ciRlt, 0L, BinaryUtils.isEmpty(ciRlt.getPublicVersion()) ? 1L : ciRlt.getPublicVersion() + 1);
                saveRltRecords.add(ciRlt);
                saveRltHisRecords.add(ciRlt);
            } else {
                boolean flag = BinaryUtils.isEmpty(ciRlt.getPublicVersion());
                EamUtil.setVersionAttrs(ciRlt,0L, BinaryUtils.isEmpty(ciRlt.getPublicVersion()) ? 1L : ciRlt.getPublicVersion());
                saveRltRecords.add(ciRlt);
                // 历史数据没有版本号字段 仅在第一次更新的时候添加到历史库
                if (flag) {
                    saveRltHisRecords.add(ciRlt);
                }
            }
        }
        if (!CollectionUtils.isEmpty(saveRltRecords)) {
            esCiRltSvc.saveOrUpdateBatch(saveRltRecords);
        }
        if (!CollectionUtils.isEmpty(saveRltHisRecords)) {
            esCIRltInfoHistorySvc.saveCIRltHistoryByCIRlts(saveRltHisRecords);
        }
    }

    @Override
    public Page<CcCiRltInfo> searchRltByBean(ESRltSearchBean bean) {
        // TODO Auto-generated method stub
        return esCiRltSvc.searchRltByBean(bean);
    }

    @Override
    public List<CcCiRltInfo> searchRltByScroll(ESRltSearchBean bean) {
        //todo
        return esCiRltSvc.searchRltByScroll(bean);
    }

    @Override
    public Page<ESCIRltInfo> searchRlt(ESRltSearchBean bean) {
        return esCiRltSvc.searchESRltByBean(bean);
    }

    /**
     * 导出ci关系excel，若需导出关系大于1W则使用空间最优策略，导出关系小于1W使用时间最优策略
     *
     * @param rltClassIds
     * @return
     */
    @Override
    public Resource exportCiRlt(Collection<Long> rltClassIds, Collection<Long> rltIds, String ownerCode) {
        if (!CollectionUtils.isEmpty(rltClassIds) && !CollectionUtils.isEmpty(rltIds)) {
            Assert.isTrue(false, "rltClassIds/rltIds不可同时使用");
        }
        // 导出数据
        if (rltClassIds != null && !rltClassIds.isEmpty()) {
            Assert.notEmpty(rltClassIds, "必须指明关系分类");
            // 不再提供策略切换，只维护空间最优策略
            return exportCiRltCoreSpaceStrategy(rltClassIds, rltIds, ownerCode);
        } else {
            // 导出模板
            return exportCiRltTemplate();
        }
    }

    /*@Override
    public Resource exportCiRlt(Collection<Long> rltClassIds) {
        return null;
    }*/

    /**
     * 构建关系的标题行
     *
     * @param titleRow 要构建的row
     * @param attrDefs 关系分类属性
     * @return 构建完的标题strings
     */
    private List<String> buildRltTitleRow(Workbook workBook, Row titleRow, List<CcCiAttrDef> attrDefs) {
        String markName = SysUtil.StaticUtil.RLTCLS_MAJOR_MARK;
        String ciCodeName = SysUtil.StaticUtil.CICODE_LABEL;
        List<String> titles = new LinkedList<>(Arrays.asList("关系ID", "源分类", "源" + ciCodeName, "源业务主键", "源CI Label",
                "目标分类", "目标" + ciCodeName, "目标业务主键", "目标CI Label"));
        List<Integer> requiredIndex = Arrays.asList(1, 2, 5, 6);
        for (int cellIndex = 0; cellIndex < titles.size(); cellIndex++) {
            Cell titleCell = titleRow.createCell(cellIndex);
            titleCell.setCellValue(titles.get(cellIndex));
            boolean required = requiredIndex.contains(cellIndex);
            FileUtil.ExcelUtil.setCellMark(workBook, titleCell, markName, false, required);
        }
        int titIndex = titles.size();
        if (attrDefs != null && attrDefs.size() > 0) {
            for (CcCiAttrDef attrDef : attrDefs) {
                Cell titleCell = titleRow.createCell(titIndex);
                titleCell.setCellValue(attrDef.getProName());
                boolean major = (attrDef.getIsMajor() != null && attrDef.getIsMajor().intValue() == 1);
                boolean required = (attrDef.getIsRequired() != null && attrDef.getIsRequired().intValue() == 1);
                FileUtil.ExcelUtil.setCellMark(workBook, titleCell, markName, major, required);
                titles.add(attrDef.getProName().toUpperCase());
                titIndex++;
            }
        }
        return titles;
    }

    /**
     * 导出模板
     *
     * @return
     */
    private Resource exportCiRltTemplate() {
        // 导出具体分类的模板
        String exportFileName = "关系一键导入模板";
        InputStream temIs = this.getClass().getResourceAsStream("/static_res/rlt_data_new_template.xlsx");
        Resource resource = new ByteArrayResource(FileUtil.readStream(temIs, true), exportFileName + ".xlsx");
        return resource;
    }

    /**
     * 导出ci关系-空间最优策略
     *
     * @param rltClassIds
     * @return
     */
    private Resource exportCiRltCoreSpaceStrategy(Collection<Long> rltClassIds, Collection<Long> rltIds, String ownerCode) {
        Resource resource = null;
        // 处理指定关系数据得情况
        Map<Long, List<ESCIRltInfo>> clsIdRltsMap = null;
        if (rltIds != null && rltIds.size() > 0) {
            List<ESCIRltInfo> rlts = esCiRltSvc
                    .getListByQuery(1, rltIds.size(), (QueryBuilders.termsQuery("id", rltIds))).getData();
            Assert.isTrue(rlts.size() == rltIds.size(), "指定得关系ids[" + JSON.toJSONString(rltIds) + "]有失效数据");
            clsIdRltsMap = rlts.stream().collect(Collectors.groupingBy(ESCIRltInfo::getClassId));
            rltClassIds = clsIdRltsMap.keySet();
        }
        // 判断入参合法性顺便取到关系分类信息，后面excel的sheet要用
        BoolQueryBuilder query = new BoolQueryBuilder();
        query.must(QueryBuilders.termsQuery("id", rltClassIds));
        if (SysUtil.StringUtil.isNotBack(ownerCode)) {
            query.must(QueryBuilders.termQuery("ownerCode", ownerCode));
        }
        List<ESCIClassInfo> rltClassInfos = esRltClassSvc.getListByQuery(query);
        Assert.isTrue(rltClassInfos != null && rltClassInfos.size() == rltClassIds.size(), "关系分类中有失效数据");
        InputStream temIs = null;
        // 待导出sheet页
        Workbook workBook = null;
        temIs = this.getClass().getResourceAsStream("/static_res/rlt_import_new_template.xlsx");
        try {
            workBook = new XSSFWorkbook(temIs);
        } catch (IOException e) {
            Assert.isTrue(false, "读取模板文件异常");
        }
        // 创建关系定义
        Sheet rltDefinition = workBook.getSheet("关系定义");
        int rltDefinitionIndex = 1;
        // 分类名称 属性名称 属性类型 方向 是否Label 是否主键
        for (ESCIClassInfo rltClassInfo : rltClassInfos) {
            List<CcCiAttrDef> attrDefs = rltClassInfo.getCcAttrDefs();
            if (attrDefs == null || attrDefs.isEmpty()) {
                Row row = rltDefinition.createRow(rltDefinitionIndex);
                row.createCell(0).setCellValue(rltClassInfo.getClassName()); // 分类名称
                rltDefinitionIndex++;
            } else {
                for (CcCiAttrDef attrDef : attrDefs) {
                    Row row = rltDefinition.createRow(rltDefinitionIndex);
                    row.createCell(0).setCellValue(rltClassInfo.getClassName()); // 分类名称
                    row.createCell(1).setCellValue(attrDef.getProName()); // 属性名称
                    row.createCell(2).setCellValue(AttrNameKeyEnum.valueOf(attrDef.getProType()).getValue()); // 属性类型
                    row.createCell(3).setCellValue(CIRltSvc.LineLabelAlignEnum.valueOf(attrDef.getLineLabelAlign()).getValue()); // 方向
                    row.createCell(4).setCellValue(attrDef.getIsCiDisp() == 0 ? "否" : "是"); // 是否Label
                    row.createCell(5).setCellValue(attrDef.getIsMajor() == 0 ? "否" : "是"); // 是否主键
                    rltDefinitionIndex++;
                }
            }
        }
        // 遍历关系分类，每一个关系分类都是一个sheet
        for (ESCIClassInfo rltCls : rltClassInfos) {
            System.gc();
            Sheet sheet = workBook.createSheet(rltCls.getClassCode());
            int rowIndex = 0;
            Row titleRow = sheet.createRow(rowIndex);
            // 表头定义-固定9个头加上关系分类的属性std
            List<String> titles = this.buildRltTitleRow(workBook, titleRow, rltCls.getCcAttrDefs());
            FileUtil.ExcelUtil.setAutoColumnSizeByRow(sheet, titleRow);
            // 获取该分类下数据-游标拉取写入
            List<ESCIRltInfo> writeCiRlts = null;
            if (rltIds == null || rltIds.size() <= 0) {
                // 指定了分类使用游标拉取
                /**
                 * {游标id:{page格式结果集}}
                 */
                Map<String, Page<ESCIRltInfo>> resultMap = esCiRltSvc.getScrollByQuery(1, 3000,
                        QueryBuilders.termQuery("classId", rltCls.getId()), "id", false);
                Assert.isTrue(resultMap != null && resultMap.size() > 0, "没游标信息和结果集？");
                Assert.isTrue(resultMap.size() == 1, "有多个游标？");
                String scrollId = null;
                for (String id : resultMap.keySet()) {
                    scrollId = id;
                }
                Page<ESCIRltInfo> pageResult = resultMap.get(scrollId);
                writeCiRlts = pageResult.getData();
                if (writeCiRlts.size() > 0) {
                    do {
                        rowIndex = wirteRltSheetRow(sheet, titles, rowIndex, writeCiRlts);
                        writeCiRlts = esCiRltSvc.getListByScroll(scrollId);
                    } while (writeCiRlts != null && writeCiRlts.size() > 0);
                }
                esCiRltSvc.clearScroll(scrollId);
            } else {
                // 指定数据id得情况，直接拉取全部
                writeCiRlts = clsIdRltsMap.get(rltCls.getId());
                rowIndex = wirteRltSheetRow(sheet, titles, rowIndex, writeCiRlts);
            }
        }
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        try {
            workBook.write(os);
            resource = new ByteArrayResource(os.toByteArray(),
                    FileUtil.ExcelUtil.getExportFileName("关系数据", ".xlsx", true));
        } catch (IOException e) {
            log.error("导出ci关系写入输出流异常", e);
        } finally {
            try {
                os.close();
                workBook.close();
            } catch (IOException e) {
                log.error("导出ci关系关闭输出流异常", e);
            }
        }
        return resource;
    }

    /**
     * 写入关系数据得sheet行
     *
     * @param sheet       待写入sheet
     * @param titles      该sheet表头
     * @param rowIndex    当前行号
     * @param writeCiRlts 待写入数据
     * @return 变更后得行号
     */
    private int wirteRltSheetRow(Sheet sheet, List<String> titles, int rowIndex, List<ESCIRltInfo> writeCiRlts) {
        // 获取这一批关系需要用到的分类信息-组装分类字典
        // 获取这一批关系需要用到的CI信息-组装CI字典
        Set<Long> ciClassIds = new HashSet<>();
        Set<Long> ciIds = new HashSet<>();
        writeCiRlts.forEach(rlt -> {
            ciClassIds.add(rlt.getSourceClassId());
            ciClassIds.add(rlt.getTargetClassId());
            ciIds.add(rlt.getSourceCiId());
            ciIds.add(rlt.getTargetCiId());
        });
        List<ESCIClassInfo> ciClassInfos = esClassSvc
                .getListByQuery(1, 99999, QueryBuilders.termsQuery("id", ciClassIds)).getData();
        Map<Long, ESCIClassInfo> ciClassMap = BinaryUtils.toObjectMap(ciClassInfos, "id");
        List<ESCIInfo> esciInfos = esCiSvc.getListByQuery(1, ciIds.size(), QueryBuilders.termsQuery("id", ciIds))
                .getData();
        Map<Object, ESCIInfo> ciMap = BinaryUtils.toObjectMap(esciInfos, "id");
        for (ESCIRltInfo ciRlt : writeCiRlts) {
            rowIndex++;
            Row dataRow = sheet.createRow(rowIndex);
            // 关系id
            dataRow.createCell(0).setCellValue(ciRlt.getId().toString());
            // 源分类
            dataRow.createCell(1).setCellValue(ciClassMap.get(ciRlt.getSourceClassId()).getClassCode());
            // // 源字段
            // dataRow.createCell(1).setCellValue("ciCode");
            // 源ciCode
            dataRow.createCell(2).setCellValue(ciRlt.getSourceCiCode());
            // 源业务主键
            ESCIInfo sourceCi = ciMap.get(ciRlt.getSourceCiId());
            if (sourceCi != null) {
                String sourceCiPrimaryKey = sourceCi.getCiPrimaryKey();
                dataRow.createCell(3).setCellValue(StringUtils.join(JSONArray.parseArray(sourceCiPrimaryKey), ","));
                // 源ciLabel
                String sourceLabel = sourceCi.getCiLabel();
                dataRow.createCell(4).setCellValue(StringUtils.join(JSONArray.parseArray(sourceLabel), ","));
            }
            // 目标分类
            dataRow.createCell(5).setCellValue(ciClassMap.get(ciRlt.getTargetClassId()).getClassCode());
            // // 目标字段
            // dataRow.createCell(4).setCellValue("ciCode");
            // 目标ciCode
            dataRow.createCell(6).setCellValue(ciRlt.getTargetCiCode());
            // 目标业务主键
            ESCIInfo targetCi = ciMap.get(ciRlt.getTargetCiId());
            if (targetCi != null) {
                String targetCiPrimaryKey = targetCi.getCiPrimaryKey();
                dataRow.createCell(7).setCellValue(StringUtils.join(JSONArray.parseArray(targetCiPrimaryKey), ","));
                // 源ciLabel
                String targetLabel = targetCi.getCiLabel();
                dataRow.createCell(8).setCellValue(StringUtils.join(JSONArray.parseArray(targetLabel), ","));
            }
            if (titles.size() > 9) {
                Map<String, String> attrs = ciRlt.getAttrs() != null ? ciRlt.getAttrs() : new HashMap<>();
                for (int cellIndex = 9; cellIndex < titles.size(); cellIndex++) {
                    String cellVal = attrs.get(titles.get(cellIndex).toUpperCase());
                    cellVal = cellVal == null ? "" : cellVal;
                    dataRow.createCell(cellIndex).setCellValue(cellVal);
                }
            }
        }
        return rowIndex;
    }

    /**
     * 根据sheet头构建属性模板s-关系的属性都为非必填
     *
     * @param
     * @return
     */
    public static List<CcCiAttrDef> getDefsBySheet(List<String> titles, int startIndex) {
        String markName = SysUtil.StaticUtil.RLTCLS_MAJOR_MARK;
        List<CcCiAttrDef> defs = new LinkedList<>();
        if (titles.size() > startIndex) {
            for (int index = startIndex; index < titles.size(); index++) {
                String originalVal = titles.get(index);
                String title = FileUtil.ExcelUtil.getCellTitleString(originalVal);
                if (title == null || "".equals(title.trim())) {
                    continue;
                }
                title = title.trim();
                CcCiAttrDef def = new CcCiAttrDef();
                defs.add(def);
                def.setId(ESUtil.getUUID());
                def.setProName(title);
                def.setProStdName(def.getProName().toUpperCase());
                def.setIsMajor(FileUtil.ExcelUtil.isMajorCell(originalVal, markName) ? 1 : 0);
                // 关系的属性都为非必填
                def.setIsRequired(0);
                // def.setIsRequired(FileUtil.ExcelUtil.isRequireCell(originalVal)
                // ? 1 : 0);
                def.setProType(3);
            }
        }
        return defs;
    }

    /**
     * 根据sheet头构建属性模板s-关系的属性都为非必填
     *
     * @param
     * @return
     */
    public static List<CcCiAttrDef> getDefsBySheet(List<ImportRltClassDto> importRltClassDtos) {
        List<CcCiAttrDef> defs = new LinkedList<>();
        importRltClassDtos.forEach(importRltClassDto -> {
            CcCiAttrDef def = new CcCiAttrDef();
            def.setId(ESUtil.getUUID());
            def.setProName(importRltClassDto.getAttrName());
            def.setProStdName(importRltClassDto.getAttrName().toUpperCase());
            def.setIsMajor(importRltClassDto.isMajor() ? 1 : 0);
            def.setIsCiDisp(importRltClassDto.islabel() ? 1 : 0);
            def.setLineLabelAlign(CIRltSvc.AttrLineLabelAlignEnum.checkAttrType(importRltClassDto.getLineLabelAlign()));
            def.setProType(AttrNameKeyEnum.checkAttrType(importRltClassDto.getAttrType()));
            defs.add(def);
        });
        return defs;
    }

    public enum AttrLineLabelAlignEnum {
        //        1=偏向源 2=偏向目标 3=居中
        SOURCE("源端", 1), TARGET("目标端", 2), CENTERED("居中", 3);

        private String value;
        private int type;

        private AttrLineLabelAlignEnum(String value, Integer type) {
            this.value = value;
            this.type = type;
        }

        public String getValue() {
            return value;
        }

        public void setValue(String value) {
            this.value = value;
        }

        public int getType() {
            return type;
        }

        public void setType(int type) {
            this.type = type;
        }

        public static IamsCIRltDesignSvc.AttrLineLabelAlignEnum valueOf(int v) {
            switch (v) {
                case 1:
                    return SOURCE;
                case 2:
                    return TARGET;
                case 3:
                    return CENTERED;

                default:
                    throw new ServiceException(" is wrong PropertyType-value '" + v + "'! ");
            }
        }

        public static Integer checkAttrType(String attrType) {
            switch (attrType) {
                case "源端":
                    return 1;
                case "目标端":
                    return 2;
                case "居中":
                    return 3;
                default:
                    return null;
            }
        }
    }

    /**
     * 全都按照csv进行处理
     */
    @Override
    public ImportResultMessage importCiRlt(Long domainId, String excelFilePath, MultipartFile excelFile, Set<String> rltClsCodes) {
        Assert.notEmpty(rltClsCodes, "请指明导入分类");
        InputStream excelIs = null;
        if (excelFile == null) {
            Assert.isTrue(excelFilePath != null && !"".equals(excelFilePath), "未传入导入文件信息");
            Assert.isTrue(excelFilePath.toLowerCase().endsWith(".xls") || excelFilePath.toLowerCase().endsWith(".xlsx"),
                    "导入文件后缀不正确（支持.xls/.xlsx）");

            rsmUtils.downloadRsmAndUpdateLocalRsm(excelFilePath);
            File file = new File(this.localPath + excelFilePath);
            Assert.isTrue(file.exists(), "导入关系文件在文件系统不存在");
            try {
                excelIs = new ByteArrayInputStream(FileUtils.readFileToByteArray(file));
            } catch (IOException e) {
                log.error("读取导入文件流异常【{}】【{}】", file.getPath(), e);
                Assert.isTrue(false, "读取导入文件流异常");
            }
        } else {
            // 简单校验文件合法性
            validImportCiRltFile(excelFile);
            try {
                excelIs = new ByteArrayInputStream(excelFile.getBytes());
            } catch (IOException e1) {
                Assert.isTrue(false, "读取excel流异常");
            }
            try {
                excelFile.getInputStream().close();
            } catch (IOException e1) {
                log.error("输入文件流已缓存到内存，关闭原始输入流异常，忽略该错误", e1);
            }
        }
        //关系定义错误记录
        ImportSheetMessage rltDefErrorMsg = new ImportSheetMessage();
        Set<String> rltClsStdCodes = new HashSet<>();
        rltClsCodes.forEach(code -> rltClsStdCodes.add(code.toUpperCase()));
        try {
            // 开始导入数据前先获取关系定义中所有关系定义
            importCiRltClsNew(domainId, excelIs, rltClsStdCodes, rltDefErrorMsg);
        } catch (Throwable e) {
            log.error("", e);
            try {
                excelIs.close();
            } catch (Exception e1) {
                log.error("关闭缓存流异常", e1);
            }
            throw e;
        }

        // 获取到所有sheet验证下合法性
        List<String> sheetNames = null;
        try {
            sheetNames = FileUtil.ExcelUtil.XLSXCovertCSVReader.readerSheetNamesByIs(excelIs, false);
            excelIs.reset();
        } catch (Exception e) {
            log.error("csv方式读取excel异常", e);
            try {
                excelIs.close();
            } catch (IOException e1) {
                log.error("关闭缓存流异常", e1);
            }
            Assert.isTrue(false, "读取excel异常");
        }

//        List<String> errorMsgs = new LinkedList<>();
        // 总/成功/失败/忽略条数
        int totalCount = 0, successCount = 0, failCount = 0, ignoreCount = 0, dbError = 0, updateNum = 0;
        Map<String, ImportSheetMessage> rltDataErrorMsgMap = new HashMap<>();
        // 挨个获取下sheet拼装字典{rltClassCode:[ImportCiRltDataDtos]}结构说明：key为关系分类，val为该关系分类下的ci关系数组
        // 为了出现问题好查，这里使用LinkedHashMap，按照sheet顺序找，以便分析分体在哪
        Map<String, List<ImportCiRltDataDto>> rltClsIdDatasDict = new LinkedHashMap<>();
        // 之后导入数据需要用到的ciCode/classCode；之后组装字典要用这些当条件查一下
        Set<String> ciCodes = new HashSet<>();
        Set<String> ciClsCodes = new HashSet<>();
        Set<String> qCiCodes = new HashSet<>();
        List<ESCIInfo> ciInfos = new LinkedList<>();
        List<FutureTask<List<ESCIInfo>>> qCiTasks = new LinkedList<>();
        try {
            // 遍历sheet
            for (String sheetName : sheetNames) {
                // 指定了关系分类但是sheet名不再指定的范围中略过
                if (!rltClsStdCodes.contains(sheetName.toUpperCase())) {
                    continue;
                }
                int sheetFailCount = 0;
                int sheetTotalCount = 0;
                ImportSheetMessage rltDataErrorMsg = new ImportSheetMessage();
                List<ImportRowMessage> rowMessages = rltDataErrorMsg.getRowMessages();
                rltDataErrorMsg.setSheetName(sheetName);
                List<String[]> rows = null;
                try {
                    rows = FileUtil.ExcelUtil.XLSXCovertCSVReader.readerExcelByIs(excelIs, sheetName, -1, false);
                    excelIs.reset();
                } catch (Exception e) {
                    log.error("sheet【{}】读取数据异常【{}】", sheetName, e);
                    continue;
                }
                // sheet页读取指针位置（第一行是标题不管，固定从第二行开始）
                int sheetReadIndex = 1;
                // sheet页最后一行
                int sheetMaxIndex = rows.size();
                // 代表只有标题没数据或者标题都没，不管
                if (sheetMaxIndex < 1) {
                    continue;
                }
                // 有一行代表至少有标题，从标题中寻找属性key
                List<String> titles = new LinkedList<>();
                for (String title : rows.get(0)) {
                    titles.add(FileUtil.ExcelUtil.getCellTitleString(title));
                }
                // 遍历sheet页中的每一数据行，组装字典数据
                for (; sheetReadIndex < sheetMaxIndex; sheetReadIndex++) {
                    List<String> rowDatas = new ArrayList<>(Arrays.asList(rows.get(sheetReadIndex)));
                    // 兼容空数据行
                    if (rowDatas.isEmpty()) {
                        continue;
                    }
                    totalCount++;
                    sheetTotalCount++;
                    // 设置旧id/sCiClassCode/sCiCode/tCiClassCode/tCiCode/关系属性
                    Long id = null;
                    if (rowDatas.get(0) != null && StringUtils.isNotBlank(rowDatas.get(0))) {
                        try {
                            id = Long.valueOf(rowDatas.get(0));
                        } catch (Exception e) {
                            log.warn("导入ci关系excel中sheet【{}】第【{}】行id列填入了错误数据【{}】", sheetName, (sheetReadIndex + 1),
                                    rowDatas.get(0));
                            ImportRowMessage rowMessage = new ImportRowMessage();
                            rowMessage.setRowNum(sheetReadIndex + 1);
                            rowMessage.setRowErrorDesc(MessageFormat.format("id列填入了错误数据【{0}】", rowDatas.get(0)));
                            rowMessages.add(rowMessage);
                            sheetFailCount++;
                            failCount++;
                            continue;
                        }
                    }
                    String sCiClassCode = rowDatas.get(1);
                    String sCiCode = rowDatas.get(2);
                    String tCiClassCode = rowDatas.get(5);
                    String tCiCode = rowDatas.get(6);
                    if (BinaryUtils.isEmpty(sCiClassCode) || BinaryUtils.isEmpty(sCiCode)
                            || BinaryUtils.isEmpty(tCiClassCode) || BinaryUtils.isEmpty(tCiClassCode)) {
                        log.warn("导入ci关系excel中sheet【{}】第【{}】行有未填写必填数据", sheetName, (sheetReadIndex + 1));
                        ImportRowMessage rowMessage = new ImportRowMessage();
                        rowMessage.setRowNum(sheetReadIndex + 1);
                        rowMessage.setRowErrorDesc("有未填写的必填数据");
                        rowMessages.add(rowMessage);
                        sheetFailCount++;
                        failCount++;
                        continue;
                    }
                    Map<String, String> attrs = new HashMap<>();
                    ImportCiRltDataDto ciRltDto = ImportCiRltDataDto.builder().id(id).sheetRowIndex(sheetReadIndex + 1)
                            .sCiClassCode(sCiClassCode).sCiCode(sCiCode).tCiClassCode(tCiClassCode).tCiCode(tCiCode)
                            .rltClassCode(sheetName.toUpperCase()).attrs(attrs).build();
                    // 拼装关系属性-从第10列开始是属性，没第10列代表没关系属性
                    if (titles.size() > 9) {
                        for (int attrIndex = 9; attrIndex < titles.size(); attrIndex++) {
                            try {
                                String titleStdName = titles.get(attrIndex).toUpperCase();
                                String attrVal = rowDatas.get(attrIndex);
                                attrs.put(titleStdName, attrVal);
                            } catch (NullPointerException e) {
                                // 这里空指针代表没对应属性，直接消化掉就好
                            }
                        }
                    }
                    // 将构建好的待导入数据加入字典中方便之后处理
                    rltClsIdDatasDict.computeIfAbsent(sheetName.toUpperCase(), k -> new LinkedList<>());
                    rltClsIdDatasDict.get(sheetName.toUpperCase()).add(ciRltDto);
                    // 标记之后需要使用的cicode和ciclscode
                    if (!ciCodes.contains(sCiCode)) {
                        ciCodes.add(sCiCode);
                        qCiCodes.add(sCiCode);
                    }
                    if (!ciCodes.contains(tCiCode)) {
                        ciCodes.add(tCiCode);
                        qCiCodes.add(tCiCode);
                    }
                    if (qCiCodes.size() > 5000) {
                        List<String> queryCiCodes = new ArrayList<>(qCiCodes);
                        Callable<List<ESCIInfo>> qCiTask = new Callable<List<ESCIInfo>>() {

                            @Override
                            public List<ESCIInfo> call() throws Exception {
                                return esCiSvc.getListByQuery(1, queryCiCodes.size(),
                                        QueryBuilders.termsQuery("ciCode.keyword", queryCiCodes)).getData();
                            }
                        };
                        FutureTask<List<ESCIInfo>> qCiTaskRes = new FutureTask<>(qCiTask);
                        qCiTaskRes.run();
                        qCiTasks.add(qCiTaskRes);
                        qCiCodes.clear();
                    }
                    ciClsCodes.add(sCiClassCode);
                    ciClsCodes.add(tCiClassCode);
                }
                rltDataErrorMsg.setFailNum(sheetFailCount);
                rltDataErrorMsg.setTotalNum(sheetTotalCount);
                rltDataErrorMsgMap.put(sheetName.toUpperCase(), rltDataErrorMsg);
            }
        } catch (Exception e) {
            try {
                excelIs.close();
            } catch (IOException e1) {
                log.error("关闭缓存流异常", e1);
            }
            throw e;
        }
        try {
            excelIs.close();
        } catch (IOException e1) {
            log.error("关闭缓存流异常", e1);
        }
        // excel中数据处理完毕，申请回收一次内存
        System.gc();
        // 拼装关系分类字典
        List<ESCIClassInfo> rltClsInfos = esRltClassSvc
                .getListByQuery(QueryBuilders.termsQuery("classStdCode.keyword", rltClsIdDatasDict.keySet()));
        // 关系分类字典{classCode:rltClsInfo}
        Map<String, ESCIClassInfo> codeRltClsInfoDict = new HashMap<>();
        rltClsInfos.forEach(rltCls -> {
            codeRltClsInfoDict.put(rltCls.getClassStdCode(), rltCls);
        });
        // 拼装ci字典{ciCode:ciInfo}
        Map<String, ESCIInfo> ciCodeCiInfoDict = new HashMap<>();
        if (qCiTasks.size() > 0) {
            for (FutureTask<List<ESCIInfo>> qCiRes : qCiTasks) {
                try {
                    List<ESCIInfo> res = qCiRes.get();
                    if (res != null && res.size() > 0) {
                        ciInfos.addAll(res);
                    }
                } catch (Exception e) {
                    Assert.isTrue(false, "拉取ci结果异常请重试");
                }
            }
        }
        if (qCiCodes.size() > 0) {
            List<ESCIInfo> cis = esCiSvc
                    .getListByQuery(1, qCiCodes.size(), QueryBuilders.termsQuery("ciCode.keyword", qCiCodes)).getData();
            if (cis != null && cis.size() > 0) {
                ciInfos.addAll(cis);
            }
        }
        ciInfos.forEach(ciInfo -> {
            ciCodeCiInfoDict.put(ciInfo.getCiCode(), ciInfo);
        });
        // 拼装ciCls字典
        Map<String, ESCIClassInfo> ciClsCodeInfoDict = new HashMap<>();
        List<ESCIClassInfo> ciClassInfos = esClassSvc
                .getListByQuery(QueryBuilders.termsQuery("classCode.keyword", ciClsCodes));
        ciClassInfos.forEach(ciCls -> {
            ciClsCodeInfoDict.put(ciCls.getClassCode(), ciCls);
        });
        // 根据拼装好的待导入字典拼装导入数据-目前策略是每一个关系分类落盘一次，若发现单关系分类下数据量过大情况可判断其中ciRltDtos分割落盘
        for (String sheetNameUpperCase : rltClsIdDatasDict.keySet()) {
            ImportSheetMessage rltDataSheetErrorMsg = rltDataErrorMsgMap.get(sheetNameUpperCase);
            Integer sheetSuccessNum = rltDataSheetErrorMsg.getSuccessNum();
            Integer sheetFailNum = rltDataSheetErrorMsg.getFailNum();
            List<ImportRowMessage> rowMessages = rltDataSheetErrorMsg.getRowMessages();
            // 该关系分类下待导入数据
            List<ImportCiRltDataDto> ciRltDtos = rltClsIdDatasDict.get(sheetNameUpperCase);
            if (codeRltClsInfoDict.get(sheetNameUpperCase) == null) {
                // 指定的关系分类不存在，直接略过
                failCount += ciRltDtos != null ? ciRltDtos.size() : 0;
                sheetFailNum += ciRltDtos != null ? ciRltDtos.size() : 0;
                log.warn("导入ci关系，指定关系分类code(与sheet名对应)【{}】不存在", sheetNameUpperCase);
                //整个sheet页错误
                rltDataSheetErrorMsg.setErrMsg(MessageFormat.format("导入ci关系，指定关系分类code(与sheet名对应)【{0}】不存在", sheetNameUpperCase));
                continue;
            }
            // 待落盘数据
            List<ESCIRltInfo> saveCiRlts = new LinkedList<>();
            // 这一批关系所属的关系分类
            ESCIClassInfo rltClsInfo = codeRltClsInfoDict.get(sheetNameUpperCase);
            Long rltClsId = rltClsInfo.getId();
            // 获取当前分类下所有已存在关系
            List<ESCIRltInfo> existRlts = new LinkedList<>();
            Long existRltCount = esCiRltSvc.countByCondition(QueryBuilders.termQuery("classId", rltClsId));
            // 若当前分类下存量关系<=3W则一次性拉取，大于3W则通过游标获取
            if (existRltCount > 0 && existRltCount <= 30000) {
                existRlts = esCiRltSvc
                        .getListByQuery(1, existRltCount.intValue(), QueryBuilders.termQuery("classId", rltClsId))
                        .getData();
            } else if (existRltCount > 0) {
                String scrollId = null;
                try {
                    Map<String, Page<ESCIRltInfo>> existScroll = esCiRltSvc.getScrollByQuery(1, 30000,
                            QueryBuilders.termQuery("classId", rltClsId), "id", true);
                    scrollId = existScroll.keySet().iterator().next();
                    if (existScroll.get(scrollId).getData() != null && existScroll.get(scrollId).getData().size() > 0) {
                        existRlts.addAll(existScroll.get(scrollId).getData());
                    }
                    while (true) {
                        List<ESCIRltInfo> nextResults = esCiRltSvc.getListByScroll(scrollId);
                        if (nextResults != null && nextResults.size() > 0) {
                            existRlts.addAll(nextResults);
                        } else {
                            break;
                        }
                    }
                } catch (Exception e) {
                    log.error("获取已存在关系异常", e);
                    Assert.isTrue(false, "server获取已存在关系异常");
                } finally {
                    if (!"".equals(scrollId)) {
                        esCiRltSvc.clearScroll(scrollId);
                    }
                }
            }
            // 已存在的关系唯一值map
            Map<String, Long> existRltStrIdMap = new HashMap<>();
            Map<String, ESCIRltInfo> existRltMap = new HashMap<>();
            //
            existRlts.forEach(rlt -> {
                existRltStrIdMap.put(rlt.getUniqueCode(), rlt.getId());
                existRltMap.put(rlt.getUniqueCode(), rlt);
            });
            existRlts.forEach(rlt -> existRltStrIdMap.put(rlt.getUniqueCode(), rlt.getId()));
            List<FutureTask<Integer>> saveTasks = new LinkedList<>();
            for (ImportCiRltDataDto ciRltDto : ciRltDtos) {
                // 本条关系源ci/ciCls----目标ci/ciCls信息
                ESCIInfo sCiInfo = ciCodeCiInfoDict.get(ciRltDto.getSCiCode());
                if (sCiInfo == null) {
                    failCount++;
                    sheetFailNum++;
                    ImportRowMessage rowMessage = new ImportRowMessage();
                    rowMessage.setRowNum(ciRltDto.getSheetRowIndex());
                    rowMessage.setRowErrorDesc(MessageFormat.format("ci关系源ciCode【{0}】不存在", ciRltDto.getSCiCode()));
                    rowMessages.add(rowMessage);
                    continue;
                }
                ESCIClassInfo sCiClsInfo = ciClsCodeInfoDict.get(ciRltDto.getSCiClassCode());
                if (sCiClsInfo == null) {
                    failCount++;
                    sheetFailNum++;
                    ImportRowMessage rowMessage = new ImportRowMessage();
                    rowMessage.setRowNum(ciRltDto.getSheetRowIndex());
                    rowMessage.setRowErrorDesc(MessageFormat.format("ci关系源ciClass【{0}】不存在", ciRltDto.getSCiClassCode()));
                    rowMessages.add(rowMessage);
                    continue;
                }
                ESCIInfo tCiInfo = ciCodeCiInfoDict.get(ciRltDto.getTCiCode());
                if (tCiInfo == null) {
                    failCount++;
                    sheetFailNum++;
                    ImportRowMessage rowMessage = new ImportRowMessage();
                    rowMessage.setRowNum(ciRltDto.getSheetRowIndex());
                    rowMessage.setRowErrorDesc(MessageFormat.format("ci关系目标ciCode【{0}】不存在", ciRltDto.getTCiCode()));
                    rowMessages.add(rowMessage);
                    continue;
                }
                ESCIClassInfo tCiClsInfo = ciClsCodeInfoDict.get(ciRltDto.getTCiClassCode());
                if (tCiClsInfo == null) {
                    failCount++;
                    sheetFailNum++;
                    ImportRowMessage rowMessage = new ImportRowMessage();
                    rowMessage.setRowNum(ciRltDto.getSheetRowIndex());
                    rowMessage.setRowErrorDesc(MessageFormat.format("ci关系目标ciClass【{0}】不存在", ciRltDto.getTCiClassCode()));
                    rowMessages.add(rowMessage);
                    continue;
                }
                Map<String, Integer> errorMsg = esCmdbCommSvc.validAttrs(rltClsInfo.getCcAttrDefs(), ciRltDto.getAttrs(),
                        true);
                if (errorMsg != null && errorMsg.size() > 0) {
                    failCount++;
                    sheetFailNum++;
                    ImportRowMessage rowMessage = new ImportRowMessage();
                    rowMessage.setRowNum(ciRltDto.getSheetRowIndex());
                    rowMessage.setRowErrorDesc(MessageFormat.format("导入ci关系属性信息不合法【{0}】", JSON.toJSONString(errorMsg.keySet())));
                    rowMessages.add(rowMessage);
                    continue;
                }
                // 到了这里代表校验都通过了，这里进行判重操作，遇到与数据库重复的则删除数据库中旧纪录存入新的，遇到与excel中重复的则忽略当条
                ESCIRltInfo saveCiRltInfo = new ESCIRltInfo();
                saveCiRltInfo.setClassId(rltClsInfo.getId());
                List<String> sourceSearchFiles = getSearchFiles(sCiClsInfo);
                List<String> targetSearchFiles = getSearchFiles(tCiClsInfo);
                saveCiRltInfo.setRltCiInfo(sCiInfo, tCiInfo,sourceSearchFiles,targetSearchFiles);
                saveCiRltInfo.setSourceCiSearchValues(sCiClsInfo.getClassName()+"_"+saveCiRltInfo.getSourceCiSearchValues());
                saveCiRltInfo.setTargetCiSearchValues(tCiClsInfo.getClassName()+"_"+saveCiRltInfo.getTargetCiSearchValues());
                saveCiRltInfo.setAttrs(ciRltDto.getAttrs());
                String uniqueCode = esCmdbCommSvc.getUniqueCode(rltClsInfo.getCcAttrDefs(), saveCiRltInfo.getAttrs(), saveCiRltInfo.getCiCode());
                saveCiRltInfo.setUniqueCode(uniqueCode);
                saveCiRltInfo.setId(ciRltDto.getId());
                String validStr = saveCiRltInfo.getUniqueCode();
                if (existRltStrIdMap.containsKey(validStr)) {
                    // 发现重复关系
                    if (existRltStrIdMap.get(validStr) == null) {
                        failCount++;
                        sheetFailNum++;
                        ImportRowMessage rowMessage = new ImportRowMessage();
                        rowMessage.setRowNum(ciRltDto.getSheetRowIndex());
                        rowMessage.setRowErrorDesc(MessageFormat.format("关系重复", JSON.toJSONString(errorMsg.keySet())));
                        rowMessages.add(rowMessage);
                        // 再excel内容里发现重复已第一次出现的为准，忽略后文
                        // ignoreCount++;
                        continue;
                    } else {
                        // 发现已存在的就把旧的id赋予(判定下与指定的id是否一致，不一直阻止操作)
                        Long existRltId = existRltStrIdMap.get(validStr);
                        if (saveCiRltInfo.getId() != null
                                && existRltId.longValue() != saveCiRltInfo.getId().longValue()) {
                            failCount++;
                            sheetFailNum++;
                            ImportRowMessage rowMessage = new ImportRowMessage();
                            rowMessage.setRowNum(ciRltDto.getSheetRowIndex());
                            rowMessage.setRowErrorDesc(MessageFormat.format("指定得关系id【{0}】与存量数据id【{1}】不一致，请检查数据", saveCiRltInfo.getId().toString(), existRltId.toString()));
                            rowMessages.add(rowMessage);
                            continue;
                        }
                        saveCiRltInfo.setId(existRltStrIdMap.get(validStr));
                        existRltStrIdMap.put(validStr, null);
                        updateNum++;
                    }
                } else {
                    existRltStrIdMap.put(validStr, null);
                }
                saveCiRlts.add(saveCiRltInfo);
                if (saveCiRlts.size() > 5000) {
                    List<ESCIRltInfo> saveDatas = new ArrayList<>(saveCiRlts);
                    Callable<Integer> task = new Callable<Integer>() {

                        @Override
                        public Integer call() throws Exception {
                            try {
                                // 历史库保存忽略没有变化的数据
                                List<ESCIRltInfo> needRecordHistoryList = new ArrayList<>();
                                // 将没有id得赋予id-关系id为关系历史得标识，保存历史必有id
                                saveDatas.forEach(rlt -> {
                                    if (rlt.getId() == null) {
                                        // 新增
                                        EamUtil.setVersionAttrs(rlt, 0L, 1L);
                                        needRecordHistoryList.add(rlt);
                                    } else {
                                        // 更新
                                        ESCIRltInfo dbCIRltInfo = existRltMap.get(rlt.getUniqueCode());
                                        if (BinaryUtils.isEmpty(dbCIRltInfo)) {
                                            EamUtil.setVersionAttrs(rlt, 0L, 1L);
                                            needRecordHistoryList.add(rlt);
                                        } else {
                                            if (!CheckAttrUtil.checkAttrMapEqual(dbCIRltInfo.getAttrs(), toStdMap(rlt.getAttrs()), false)) {
                                                EamUtil.setVersionAttrs(rlt, 0L, BinaryUtils.isEmpty(dbCIRltInfo.getPublicVersion()) ? 1L : dbCIRltInfo.getPublicVersion() + 1);
                                                needRecordHistoryList.add(rlt);
                                            } else {
                                                EamUtil.setVersionAttrs(rlt,0L, BinaryUtils.isEmpty(dbCIRltInfo.getPublicVersion()) ? 1L : dbCIRltInfo.getPublicVersion());
                                            }
                                        }
                                    }
                                });
//                                saveDatas.forEach(
//                                        rlt -> rlt.setId(rlt.getId() == null ? ESUtil.getUUID() : rlt.getId()));
                                esCiRltSvc.saveOrUpdateBatch(saveDatas);
                                esCIRltInfoHistorySvc.saveCIRltHistoryByCIRlts(needRecordHistoryList);
                                return saveDatas.size();
                            } catch (Exception e) {
                                return 0;
                            }
                        }
                    };
                    FutureTask<Integer> saveTask = new FutureTask<>(task);
                    saveTask.run();
                    saveTasks.add(saveTask);
                    saveCiRlts.clear();
                }
            }
            if (saveCiRlts != null && saveCiRlts.size() > 0) {
                // 历史库保存忽略没有变化的数据
                List<ESCIRltInfo> needRecordHistoryList = new ArrayList<>();
                saveCiRlts.forEach(rlt -> {
                    if (rlt.getId() == null) {
                        rlt.setId(ESUtil.getUUID());
                        EamUtil.setVersionAttrs(rlt, 0L, 1L);
                        needRecordHistoryList.add(rlt);
                    } else {
                        // 更新
                        ESCIRltInfo dbCIRltInfo = existRltMap.get(rlt.getUniqueCode());
                        if (BinaryUtils.isEmpty(dbCIRltInfo)) {
                            EamUtil.setVersionAttrs(rlt, 0L, 1L);
                            needRecordHistoryList.add(rlt);
                        } else {
                            if (!CheckAttrUtil.checkAttrMapEqual(dbCIRltInfo.getAttrs(), toStdMap(rlt.getAttrs()), false)) {
                                EamUtil.setVersionAttrs(rlt,0L, BinaryUtils.isEmpty(dbCIRltInfo.getPublicVersion()) ? 1L : dbCIRltInfo.getPublicVersion() + 1);
                                needRecordHistoryList.add(rlt);
                            } else {
                                EamUtil.setVersionAttrs(rlt,0L, BinaryUtils.isEmpty(dbCIRltInfo.getPublicVersion()) ? 1L : dbCIRltInfo.getPublicVersion());
                            }
                        }
                    }
                });
                // saveCiRlts.forEach(rlt -> rlt.setId(rlt.getId() == null ? ESUtil.getUUID() : rlt.getId()));
                esCiRltSvc.saveOrUpdateBatch(saveCiRlts);
                esCIRltInfoHistorySvc.saveCIRltHistoryByCIRlts(needRecordHistoryList);
                successCount += saveCiRlts.size();
                sheetSuccessNum += saveCiRlts.size();
                // 拉取之前异步的结果
                for (FutureTask<Integer> taskRes : saveTasks) {
                    try {
                        successCount += taskRes.get();
                        sheetSuccessNum += taskRes.get();
                    } catch (Exception e) {
                        log.error("拉取异步存储结果异常", e);
                    }
                }
            }
            rltDataSheetErrorMsg.setSuccessNum(sheetSuccessNum);
            rltDataSheetErrorMsg.setFailNum(sheetFailNum);
        }
        ImportResultMessage importResult = ImportResultMessage.builder()
                .defTotalNum(rltDefErrorMsg.getTotalNum())
                .defSuccessNum(rltDefErrorMsg.getSuccessNum())
                .defFailNum(rltDefErrorMsg.getFailNum())
                .successNum(successCount)
                .updateNum(updateNum)
                .insertNum(successCount - updateNum)
                .failNum(failCount)
                .totalNum(totalCount)
                .build();
//        rltDataErrorMsgMap
//        rltDefErrorMsg
        if (!rltDefErrorMsg.getRowMessages().isEmpty() || !rltDataErrorMsgMap.isEmpty()) {
            // 创造出该虚拟文件夹下操作对应的文件系统里的文件夹
            Long dateTimeFolder = ESUtil.getNumberDate();

            String filePath = "/" + dateTimeFolder + "/" + "CI关系导入明细" + "_"
                    + new SimpleDateFormat("yyyyMMdd-HHmmss").format(new Date()) + ".xlsx";
            File output = new File(localPath + filePath);
            if (!output.getParentFile().exists()) {
                output.getParentFile().mkdirs();
            }

            try( Workbook swb = new XSSFWorkbook();
                 FileOutputStream fileOutputStream = new FileOutputStream(output);
            ) {
                Sheet rltDefSheet = swb.createSheet("关系定义");
                Row rltDefRow0 = rltDefSheet.createRow(0);
                Cell rltDefCell_0_0 = rltDefRow0.createCell(0, CellType.STRING);
                rltDefCell_0_0.setCellValue("关系定义-导入明细");
                Row rltDefRow1 = rltDefSheet.createRow(1);
                Cell rltDefCell_1_0 = rltDefRow1.createCell(0, CellType.STRING);
                rltDefCell_1_0.setCellValue(MessageFormat.format("成功：{0}/{1}", rltDefErrorMsg.getSuccessNum(), rltDefErrorMsg.getTotalNum()));
                Row rltDefRow2 = rltDefSheet.createRow(2);
                Cell rltDefCell_2_0 = rltDefRow2.createCell(0, CellType.STRING);
                rltDefCell_2_0.setCellValue(MessageFormat.format("失败：{0}/{1}", rltDefErrorMsg.getFailNum(), rltDefErrorMsg.getTotalNum()));

                for (int i = 0; i < rltDefErrorMsg.getRowMessages().size(); i++) {
                    ImportRowMessage rowMessage = rltDefErrorMsg.getRowMessages().get(i);
                    Row row = rltDefSheet.createRow(i + 3);
                    Cell cell0 = row.createCell(0, CellType.STRING);
                    cell0.setCellValue(MessageFormat.format("第{0}行", rowMessage.getRowNum()));
                    Cell cell1 = row.createCell(1, CellType.STRING);
                    cell1.setCellValue(rowMessage.getRowErrorDesc());
                }
                //关系数据
                rltDataErrorMsgMap.forEach((sheetNameUpperCase, rltDataErrorMsg) -> {
                    Sheet sheet = swb.createSheet(rltDataErrorMsg.getSheetName());
                    Row row0 = sheet.createRow(0);
                    Cell cell_0_0 = row0.createCell(0, CellType.STRING);
                    cell_0_0.setCellValue(rltDataErrorMsg.getSheetName() + "-导入明细");
                    Row row1 = sheet.createRow(1);
                    Cell cell_1_0 = row1.createCell(0, CellType.STRING);
                    cell_1_0.setCellValue(MessageFormat.format("成功：{0}/{1}", rltDataErrorMsg.getSuccessNum(), rltDataErrorMsg.getTotalNum()));
                    Row row2 = sheet.createRow(2);
                    Cell cell_2_0 = row2.createCell(0, CellType.STRING);
                    cell_2_0.setCellValue(MessageFormat.format("失败：{0}/{1}", rltDataErrorMsg.getFailNum(), rltDataErrorMsg.getTotalNum()));

                    for (int i = 0; i < rltDataErrorMsg.getRowMessages().size(); i++) {
                        ImportRowMessage rowMessage = rltDataErrorMsg.getRowMessages().get(i);
                        Row row = sheet.createRow(i + 3);
                        Cell cell0 = row.createCell(0, CellType.STRING);
                        cell0.setCellValue(MessageFormat.format("第{0}行", rowMessage.getRowNum()));
                        Cell cell1 = row.createCell(1, CellType.STRING);
                        cell1.setCellValue(rowMessage.getRowErrorDesc());
                    }
                });
                swb.write(fileOutputStream);

                rsmUtils.uploadRsmFromFile(output);
            } catch (Exception e) {
                log.error("写入导入关系异常文件异常", e);
            }
            importResult.setFailFile(urlPath + filePath);
        }
        return importResult;
    }

    /**
     * 根据关系定义导入关系分类
     *
     * @param is
     */
    private void importCiRltClsNew(Long domainId, InputStream is, Set<String> rltClsStdCodes, ImportSheetMessage rltDefErrorMsg) {

        BoolQueryBuilder rltClsQuery = QueryBuilders.boolQuery();
        if (rltClsStdCodes.size() > 0) {
            rltClsQuery.must(QueryBuilders.termsQuery("classStdCode.keyword", rltClsStdCodes));
        }
        Set<String> existClsStdCodes = new HashSet<>();
        esRltClassSvc.getListByQuery(rltClsQuery).forEach(cls -> existClsStdCodes.add(cls.getClassStdCode()));


        List<ImportRowMessage> rowMessages = rltDefErrorMsg.getRowMessages();

        String clsDefKey = SysUtil.StaticUtil.RLT_DEF;
        List<String[]> rows = null;
        try {
            rows = FileUtil.ExcelUtil.XLSXCovertCSVReader.readerExcelByIs(is, clsDefKey, -1, false);
            is.reset();
        } catch (Exception e) {
            log.error("sheet【{}】读取数据异常【{}】", clsDefKey, e);
        }

        if (rows == null || rows.size() <= 1) {
            return;
        }
        List<ImportRltClassDto> importRltClassDtoList = new ArrayList<>();
        //记录分类属性判断是否重名
        Map<String, Set<String>> attrByClsNameMap = new HashMap<>();
        //记录属性异常的分类名，方便后续mimportRltClassDtoMap剔除该分类
        Set<String> errorRltNames = new HashSet<>();
        Set<String> successRltNames = new HashSet<>();
        for (int i = 1; i < rows.size(); i++) {
            String[] row = rows.get(i);
            //只处理选择的分类
            if (row[0] != null && rltClsStdCodes.contains(row[0].toUpperCase())) {
                if (existClsStdCodes.contains(row[0].toUpperCase())) {
                    //分类已存在
                    errorRltNames.add(row[0]);
                    ImportRowMessage rowMessage = new ImportRowMessage();
                    rowMessage.setRowNum(i + 1);
                    rowMessage.setRowErrorDesc("分类已存在");
                    rowMessages.add(rowMessage);
                } else {
                    ImportRltClassDto importRltClassDto;
                    if (row[1] == null && row[2] == null) {
                        //无属性分类
                        importRltClassDto = ImportRltClassDto.builder().index(i).className(row[0]).build();
                    } else {
                        if (row[1] == null) {
                            errorRltNames.add(row[0]);
                            ImportRowMessage rowMessage = new ImportRowMessage();
                            rowMessage.setRowNum(i + 1);
                            rowMessage.setRowErrorDesc("属性名称不能为空");
                            rowMessages.add(rowMessage);
                            continue;
                        }
                        //判断属性名是否重复
                        if (attrByClsNameMap.get(row[0]) != null && attrByClsNameMap.get(row[0]).contains(row[1].toUpperCase())) {
                            errorRltNames.add(row[0]);
                            ImportRowMessage rowMessage = new ImportRowMessage();
                            rowMessage.setRowNum(i + 1);
                            rowMessage.setRowErrorDesc(MessageFormat.format("属性名称[{0}]重复", row[1]));
                            rowMessages.add(rowMessage);
                            continue;
                        }
                        importRltClassDto = ImportRltClassDto.builder()
                                .index(i)
                                .className(row[0])
                                .attrName(row[1])
                                .attrType(row[2] == null ? "字符串" : row[2])
                                .lineLabelAlign(row[3] == null ? "源端" : row[3])
                                .isLabel(row[4] == null ? "否" : row[4])
                                .isMajor(row[5] == null ? "否" : row[5])
                                .build();
                        try {
                            // 验证类定义填写规范,此处校验了必填项与所填内容的合法性
                            importRltClassDto.valid();
                        } catch (Exception e) {
                            errorRltNames.add(row[0]);
                            ImportRowMessage rowMessage = new ImportRowMessage();
                            rowMessage.setRowNum(i + 1);
                            rowMessage.setRowErrorDesc(e.getMessage());
                            rowMessages.add(rowMessage);
                            continue;
                        }
                        Set<String> attrNameSet = attrByClsNameMap.computeIfAbsent(importRltClassDto.getClassName(), k -> new HashSet<>());
                        attrNameSet.add(importRltClassDto.getAttrName().toUpperCase());
                    }
                    importRltClassDtoList.add(importRltClassDto);
                    successRltNames.add(row[0]);
                }
            }
        }
        Map<String, List<ImportRltClassDto>> importRltClassDtoMap = new HashMap<>();
        importRltClassDtoList.forEach(importRltClassDto -> {
            if (!errorRltNames.contains(importRltClassDto.getClassName())) {
                List<ImportRltClassDto> list = importRltClassDtoMap.computeIfAbsent(importRltClassDto.getClassName(), k -> new ArrayList<>());
                if (importRltClassDto.getAttrName() != null) {
                    list.add(importRltClassDto);
                }
            }
        });

        List<ESCIClassInfo> saveDtos = new LinkedList<>();
        for (Map.Entry<String, List<ImportRltClassDto>> entry : importRltClassDtoMap.entrySet()) {
            String className = entry.getKey();
            List<ImportRltClassDto> list = entry.getValue();
            ESCIClassInfo saveDto = new ESCIClassInfo();
            saveDto.setClassCode(className);
            saveDto.setClassStdCode(className.toUpperCase());
            saveDto.setClassName(className);
            if (!list.isEmpty()) {
                saveDto.setCcAttrDefs(getDefsBySheet(list));
            }
            saveDtos.add(saveDto);
        }
        // 设置orderNo
        AtomicInteger orderNo = new AtomicInteger(esRltClassSvc.getNextOrderNo(domainId));
        saveDtos.stream().filter(esCiClass -> esCiClass.getOrderNo() == null)
                .forEach(esCiClass -> esCiClass.setOrderNo(orderNo.getAndIncrement()));
        esRltClassSvc.saveOrUpdateBatch(saveDtos);
        rltDefErrorMsg.setSuccessNum(successRltNames.size());
        rltDefErrorMsg.setFailNum(errorRltNames.size());
        rltDefErrorMsg.setTotalNum(rltDefErrorMsg.getSuccessNum() + rltDefErrorMsg.getFailNum());
    }

    /**
     * 根据导入ci关系excel获取工作簿
     *
     * @param excelFile
     * @return
     */
    private Workbook getWorkBookByImportCiRltFile(MultipartFile excelFile) {
        // 待返回工作簿
        Workbook workbook = null;
        // 获取文件名，区分两种文件以不同方式获取工作簿
        String fileNameLow = excelFile.getOriginalFilename().toLowerCase().trim();
        try {
            InputStream inStream = excelFile.getInputStream();
            if (fileNameLow.endsWith(".xls")) {
                workbook = new HSSFWorkbook(inStream);
            } else if (fileNameLow.endsWith(".xlsx")) {
                workbook = new XSSFWorkbook(inStream);
            }
        } catch (Exception e) {
            log.error("读取导入文件/从文件读取到工作簿异常【{}】", e);
            throw new RuntimeException(e.getMessage(), e);
        }
        return workbook;
    }

    /**
     * 校验导入关系文件
     *
     * @param excelFile
     */
    protected void validImportCiRltFile(MultipartFile excelFile) {
        Assert.notNull(excelFile, "导入文件不得为空");
        String fileName = excelFile.getOriginalFilename();
        Assert.isTrue(fileName.toLowerCase().endsWith(".xls") || fileName.toLowerCase().endsWith(".xlsx"),
                "导入文件后缀不正确（支持.xls/.xlsx）");
    }

    @Override
    public Integer delRltByIdsOrRltCodes(Collection<Long> rltIds, Collection<String> rltCodes, String ownerCode) {
        Assert.isTrue((rltIds != null && rltIds.size() > 0) || (rltCodes != null && rltCodes.size() > 0), "入参不得为空");
        BoolQueryBuilder delQuery = QueryBuilders.boolQuery();
        BoolQueryBuilder delHisQuery = QueryBuilders.boolQuery();
        if (rltIds != null && rltIds.size() > 0) {
            delQuery.should(QueryBuilders.termsQuery("id", rltIds));
            delHisQuery.should(QueryBuilders.termsQuery("rltId", rltIds));
        }
        if (rltCodes != null && rltCodes.size() > 0) {
            if(BinaryUtils.isEmpty(ownerCode)){
                delQuery.must(QueryBuilders.termQuery("ownerCode.keyword", ownerCode));
            }
            delQuery.should(QueryBuilders.termsQuery("ciCode.keyword", rltCodes));
            delHisQuery.should(QueryBuilders.termsQuery("ciCode.keyword", rltCodes));
        }
        esCIRltInfoHistorySvc.saveCIRltHistoryByRltQuery(delQuery, ESCIRltInfoHistorySvc.ActionType.DELETE);
        Integer res = esCiRltSvc.deleteByQuery(delQuery, true);
        // esCIRltInfoHistorySvc.deleteByQuery(delHisQuery, true);

        try {
            iamsCIRltDesignNonComplianceDao.deleteByQuery(delQuery, true);
        } catch (Exception e) {
            e.printStackTrace();
        }

        return res;
    }

    @Override
    public Map<String, Boolean> comprehendRltExcel(MultipartFile excelFile) {
        Map<String, Boolean> clsCodeExistMap = new LinkedHashMap<>();
        Assert.notNull(excelFile, "excecl不得为空");
        // 简单校验文件合法性
        validImportCiRltFile(excelFile);
        try {
            // sheet中的clscodes
            List<String> sheetNames = FileUtil.ExcelUtil.XLSXCovertCSVReader
                    .readerSheetNamesByIs(excelFile.getInputStream(), true);
            sheetNames = sheetNames.stream().filter(name -> !SysUtil.StaticUtil.README_SHEETNAME.equals(name))
                    .collect(Collectors.toList());
            Assert.notEmpty(sheetNames, "没有检索到sheet页");
            Set<String> existCodes = new HashSet<>();
            esRltClassSvc.getListByQuery(QueryBuilders.termsQuery("classCode.keyword", sheetNames))
                    .forEach(cls -> existCodes.add(cls.getClassCode().toUpperCase()));
            sheetNames
                    .forEach(sheetName -> clsCodeExistMap.put(sheetName, existCodes.contains(sheetName.toUpperCase())));
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return clsCodeExistMap;
    }

    @Override
    public ImportExcelMessage parseRltExcel(String excelFilePath, MultipartFile excelFile) {
        ImportExcelMessage res = ImportExcelMessage.builder().build();
        InputStream inputStream = null;
        File storeFile = null;
        if (excelFile != null) {
            validImportCiRltFile(excelFile);
            try {
                storeFile = new File(this.localPath + "/" + UUID.randomUUID() + ".xlsx");
                FileUtil.writeFile(storeFile, excelFile.getBytes());
                inputStream = excelFile.getInputStream();
            } catch (IOException e) {
                Assert.isTrue(false, "解析excel输入流异常");
            }
        } else {
            Assert.isTrue(excelFilePath != null && !"".equals(excelFilePath), "上传解析excel路径为空 ");
            Assert.isTrue(excelFilePath.toLowerCase().endsWith(".xls") || excelFilePath.toLowerCase().endsWith(".xlsx"),
                    "导入文件后缀不正确（支持.xls/.xlsx）");
            rsmUtils.downloadRsmAndUpdateLocalRsm(excelFilePath);
            storeFile = new File(this.localPath + excelFilePath);
            Assert.isTrue(storeFile.exists(), "上传解析excel不存在");
            try {
                inputStream = new FileInputStream(storeFile);
            } catch (FileNotFoundException e) {
                Assert.isTrue(false, "上传解析excel不存在");
            }
        }
        res.setFileName(storeFile.getPath().replaceFirst(this.localPath, ""));
        res.setOriginalFileName(excelFile.getOriginalFilename());
        try {
            // sheet中的clscodes
            List<String> sheetNames = FileUtil.ExcelUtil.XLSXCovertCSVReader.readerSheetNamesByIs(inputStream, true);
            sheetNames = sheetNames.stream().filter(name -> !SysUtil.StaticUtil.README_SHEETNAME.equals(name) && !SysUtil.StaticUtil.RLT_DEF.equals(name))
                    .collect(Collectors.toList());
            Assert.notEmpty(sheetNames, "没有检索到sheet页");
            sheetNames.forEach(sheetName -> res.getSheetNames().add(sheetName));
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return res;
    }

    @Override
    public List<CcCiRltInfo> searchRltByIds(Set<Long> ids) {
        Assert.notEmpty(ids, "params ids not empty");
        return esCmdbCommSvc
                .tranCcCiRltInfoPage(esCiRltSvc.getListByQuery(1, ids.size(), QueryBuilders.termsQuery("id", ids)))
                .getData();
    }

    @Override
    public Integer delUserRlts(Collection<Long> rltClsIds, String ownerCode, Collection<Long> sourceCIClsIds, Collection<Long> targetCIClsIds) {
        Assert.isTrue(
                (rltClsIds != null && !rltClsIds.isEmpty()) || (sourceCIClsIds != null && !sourceCIClsIds.isEmpty())
                        || (targetCIClsIds != null && !targetCIClsIds.isEmpty()),
                "request del param don't empty");
        BoolQueryBuilder query = QueryBuilders.boolQuery();
        if (rltClsIds != null && !rltClsIds.isEmpty()) {
            query.must(QueryBuilders.termsQuery("classId", rltClsIds));
        }
        if (sourceCIClsIds != null && !sourceCIClsIds.isEmpty()) {
            query.must(QueryBuilders.termsQuery("sourceClassId", sourceCIClsIds));
        }
        if (targetCIClsIds != null && !targetCIClsIds.isEmpty()) {
            query.must(QueryBuilders.termsQuery("targetClassId", targetCIClsIds));
        }
        esCIRltInfoHistorySvc.saveCIRltHistoryByRltQuery(query, ESCIRltInfoHistorySvc.ActionType.DELETE);
        Integer res = esCiRltSvc.deleteByQuery(query, true);
        // esCIRltInfoHistorySvc.deleteByQuery(query, true);
        return res;
    }


    @Override
    public ImportResultMessage bindCiRlts(Long domainId, Set<BindCiRltRequestDto> bindRltDtos, boolean repeat,
                                          Map<Long, ESCIInfo> idCiMap, Map<Long, ESCIClassInfo> idRltClsMap) {
        Assert.notEmpty(bindRltDtos, "request param not empty");
        ImportResultMessage res = new ImportResultMessage();
        bindRltDtos.forEach(dto -> ValidDtoUtil.valid(dto));
        if (idCiMap == null) {
            idCiMap = new HashMap<>();
            for (BindCiRltRequestDto dto : bindRltDtos) {
                idCiMap.put(dto.getSourceCiId(), null);
                idCiMap.put(dto.getTargetCiId(), null);
            }
            List<ESCIInfo> cis = esCiSvc
                    .getListByQuery(1, idCiMap.keySet().size(), QueryBuilders.termsQuery("id", idCiMap.keySet()))
                    .getData();
            for (ESCIInfo ci : cis) {
                idCiMap.put(ci.getId(), ci);
            }
        }
        if (idRltClsMap == null) {
            idRltClsMap = new HashMap<>();
            for (BindCiRltRequestDto dto : bindRltDtos) {
                idRltClsMap.put(dto.getRltClassId(), null);
            }
            List<ESCIClassInfo> rltClss = esRltClassSvc.getListByQuery(1, idRltClsMap.keySet().size(),
                    QueryBuilders.termsQuery("id", idRltClsMap.keySet())).getData();
            for (ESCIClassInfo rltCls : rltClss) {
                idRltClsMap.put(rltCls.getId(), rltCls);
            }
        }
        List<ESCIRltInfo> importRlts = new LinkedList<>();
        Set<String> importUniqueCodes = new HashSet<>();
        for (BindCiRltRequestDto bindDto : bindRltDtos) {
            ESCIClassInfo rltCls = idRltClsMap.get(bindDto.getRltClassId());
            ESCIInfo sourceCi = idCiMap.get(bindDto.getSourceCiId());
            ESCIClassInfo sourceCiClass = esClassSvc.getById(sourceCi.getClassId());
            List<String> sourceSearchFiles = getSearchFiles(sourceCiClass);
            ESCIInfo targetCi = idCiMap.get(bindDto.getTargetCiId());
            ESCIClassInfo targetCiClass = esClassSvc.getById(targetCi.getClassId());
            List<String> targetSearchFiles = getSearchFiles(targetCiClass);
            Map<String, Integer> errorRes = esCmdbCommSvc.validAttrs(rltCls.getCcAttrDefs(), bindDto.getAttrs(), true);
            boolean isError = rltCls == null || sourceCi == null || targetCi == null
                    || (errorRes != null && errorRes.size() > 0);
            if (isError) {
                res.setFailNum(res.getFailNum() + 1);
            } else {
                ESCIRltInfo importRlt = new ESCIRltInfo();
                importRlt.setAttrs(bindDto.getAttrs());
                importRlt.setClassId(bindDto.getRltClassId());
                importRlt.setRltCiInfo(sourceCi, targetCi,sourceSearchFiles,targetSearchFiles);
                importRlt.setSourceCiSearchValues(sourceCiClass.getClassName()+"_"+importRlt.getSourceCiSearchValues());
                importRlt.setTargetCiSearchValues(targetCiClass.getClassName()+"_"+importRlt.getTargetCiSearchValues());
                String uniqueCode = esCmdbCommSvc.getUniqueCode(rltCls.getCcAttrDefs(), importRlt.getAttrs(), importRlt.getCiCode());
                importRlt.setUniqueCode(uniqueCode);
                if (!importUniqueCodes.contains(uniqueCode)) {
                    importRlts.add(importRlt);
                    importUniqueCodes.add(uniqueCode);
                }
            }
        }
        // 构建{已存在关系uniquecode:关系信息}字典
        Map<String, ESCIRltInfo> existRltUniqueCodeEntityMap = new HashMap<>();
        List<ESCIRltInfo> existRlts = esCiRltSvc.getListByQuery(1, importUniqueCodes.size(),
                QueryBuilders.termsQuery("uniqueCode.keyword", importUniqueCodes)).getData();
        existRlts.forEach(existRlt -> existRltUniqueCodeEntityMap.put(existRlt.getUniqueCode(), existRlt));
        // 历史库保存忽略没有变化的数据
        List<ESCIRltInfo> needRecordHistoryList = new ArrayList<>();
        if (repeat) {
            // 重复关系覆盖
            if (importRlts != null && importRlts.size() > 0) {
                importRlts.forEach(rlt -> {
                    if (existRltUniqueCodeEntityMap.keySet().contains(rlt.getUniqueCode())) {
                        rlt.setId(existRltUniqueCodeEntityMap.get(rlt.getUniqueCode()).getId());
                        ESCIRltInfo dbCIRltInfo = existRltUniqueCodeEntityMap.get(rlt.getUniqueCode());
                        if (!CheckAttrUtil.checkAttrMapEqual(dbCIRltInfo.getAttrs(), toStdMap(rlt.getAttrs()), false)) {
                            EamUtil.setVersionAttrs(rlt,0L, BinaryUtils.isEmpty(dbCIRltInfo.getPublicVersion()) ? 1L : dbCIRltInfo.getPublicVersion() + 1);
                            needRecordHistoryList.add(rlt);
                        } else {
                            EamUtil.setVersionAttrs(rlt,0L, BinaryUtils.isEmpty(dbCIRltInfo.getPublicVersion()) ? 1L : dbCIRltInfo.getPublicVersion());
                        }
                    } else {
                        rlt.setId(ESUtil.getUUID());
                        EamUtil.setVersionAttrs(rlt, 0L, 1L);
                        needRecordHistoryList.add(rlt);
                    }
                });
                esCiRltSvc.saveOrUpdateBatch(importRlts);
                esCIRltInfoHistorySvc.saveCIRltHistoryByCIRlts(needRecordHistoryList);
            }
        } else {
            // 重复关系掠过
            importRlts.removeIf(importRlt -> existRltUniqueCodeEntityMap.keySet().contains(importRlt.getUniqueCode()));
            if (importRlts != null && importRlts.size() > 0) {
                importRlts.forEach(rlt -> {
                    rlt.setId(ESUtil.getUUID());
                    EamUtil.setVersionAttrs(rlt, 0L, 1L);
                });
                // importRlts.forEach(rlt -> rlt.setId(ESUtil.getUUID()));
                esCiRltSvc.saveOrUpdateBatch(importRlts);
                esCIRltInfoHistorySvc.saveCIRltHistoryByCIRlts(importRlts);
            }
        }
        res.setSuccessNum(importRlts.size());
        res.setFailNum(bindRltDtos.size() - importRlts.size());
        return res;
    }

    @Override
    public Map<Long, List<ESCIRltInfoHistory>> getRltsHistrysDict(Set<Long> rltIds, boolean hasCurrent) {
        Assert.notEmpty(rltIds, "rltIds not null");
        Map<Long, List<ESCIRltInfoHistory>> res = new HashMap<>();
        BoolQueryBuilder rltHistoryQuery = QueryBuilders.boolQuery();
        rltHistoryQuery.must(QueryBuilders.termsQuery("rltId", rltIds));
        Long matchCount = esCIRltInfoHistorySvc.countByCondition(rltHistoryQuery);
        if (matchCount <= 0) {
            return res;
        }
        Page<ESCIRltInfoHistory> historys = esCIRltInfoHistorySvc.getSortListByQuery(1, matchCount.intValue(),
                rltHistoryQuery, "version", true);
        res = historys.getData().stream().collect(Collectors.groupingBy(ESCIRltInfoHistory::getRltId));
        if (!hasCurrent) {
            // 不需要包含当前就处理下把每个rltid里最大版本剔除,若剔除完该rlt历史版本没有了则剔除这个key
            res.values().forEach(rltHistorys -> rltHistorys.remove(rltHistorys.size() - 1));
            res.entrySet().removeIf(entry -> entry.getValue() == null || entry.getValue().size() <= 0);
        }
        return res;
    }

    @Override
    public Map<Long, Long> getRltIdMaxVersion(Set<Long> rltIds) {
        return null;
    }

    @Override
    public Page<String> groupByField(Long domainId, ESAttrAggBean req) {
        Long classId = req.getClassId();
        String field = req.getAttrName();
        Assert.notNull(classId, "X_PARAM_NOT_NULL${name:classId}");
        Assert.notNull(field, "X_PARAM_NOT_NULL${name:attrName}");
        return esCiRltSvc.queryAttrVal(domainId, req);
    }

    @Override
    public Map<Long, Map<Long, Set<Long>>> getClassRltMapByClsQuery(Set<Long> clsIds, Set<Long> rltClsIds) {
        Assert.notEmpty(clsIds, "param not empty");
        Map<Long, Map<Long, Set<Long>>> sourceClsIdTargetsMap = new LinkedHashMap<>();
        clsIds.forEach(clsId -> sourceClsIdTargetsMap.put(clsId, new LinkedHashMap<>()));
        BoolQueryBuilder rltQuery = QueryBuilders.boolQuery();
        rltQuery.must(QueryBuilders.termsQuery("sourceClassId", clsIds))
                .must(QueryBuilders.termsQuery("targetClassId", clsIds));
        if (rltClsIds != null && rltClsIds.size() > 0) {
            rltQuery.must(QueryBuilders.termsQuery("classId", rltClsIds));
        }
        long rltNum = esCiRltSvc.countByCondition(rltQuery);
        if (rltNum > 0L) {
            Page<ESCIRltInfo> rlts = esCiRltSvc.getListByQuery(1, new BigDecimal(rltNum).intValue(), rltQuery);
            rlts.getData().stream().collect(Collectors.groupingBy(ESCIRltInfo::getSourceClassId))
                    .forEach((sourceClsId, targets) -> {
                        Map<Long, Set<Long>> rltIdTargetIdsMap = sourceClsIdTargetsMap.get(sourceClsId);
                        targets.stream().collect(Collectors.groupingBy(ESCIRltInfo::getClassId))
                                .forEach((rltClsId, rltTargets) -> {
                                    if (rltIdTargetIdsMap.get(rltClsId) == null) {
                                        rltIdTargetIdsMap.put(rltClsId, new LinkedHashSet<>());
                                    }
                                    rltIdTargetIdsMap.get(rltClsId).addAll(rltTargets.stream()
                                            .collect(Collectors.groupingBy(ESCIRltInfo::getTargetClassId)).keySet());
                                });
                    });
        }
        return sourceClsIdTargetsMap;
    }

    @Override
    public List<DataModuleRltClassDto> getClassRltList(Set<Long> classIds, Set<Long> rltClsIds) {
        // 查询分类关系{源分类id:{关系分类id:[目标分类ids]}}
        Map<Long, Map<Long, Set<Long>>> allRltMap = this.getClassRltMapByClsQuery(classIds, rltClsIds);

        // 获取到所有的关系分类ID
        Set<Long> rltClsIdsInResult = new HashSet<>();
        allRltMap.forEach((allRltMapKey, rltClsTargetCiClsMap) -> rltClsIdsInResult.addAll(rltClsTargetCiClsMap.keySet()));

        // 查询到关系分类列表
        CCcCiClass cdt = new CCcCiClass();
        cdt.setIds(rltClsIdsInResult.toArray(new Long[0]));
        List<CcCiClassInfo> rltClassInfos = new ArrayList<>();
        BoolQueryBuilder query = ESUtil.cdtToBuilder(cdt);
        long count = esRltClassSvc.countByCondition(query);
        if (count > 0) {
            rltClassInfos = esRltClassSvc.queryClassByCdt(1, new BigDecimal(count).intValue(), cdt);
        }
        Map<Long, CcCiClassInfo> rltClassMap = new HashMap<>();
        for (CcCiClassInfo rltClassInfo : rltClassInfos) {
            rltClassMap.put(rltClassInfo.getCiClass().getId(), rltClassInfo);
        }
        List<DataModuleRltClassDto> retList = new LinkedList<>();
        allRltMap.forEach((allRltMapKey, rltClsTargetCiClsMap) -> rltClsTargetCiClsMap.forEach((rltClassId, targetClassIdSet) -> {
            for (long targetClassId : targetClassIdSet) {
                DataModuleRltClassDto dataModuleRltClassDto = new DataModuleRltClassDto();
                dataModuleRltClassDto.setSourceClassId(allRltMapKey);
                dataModuleRltClassDto.setTargetClassId(targetClassId);
                dataModuleRltClassDto.setRltClassInfo(rltClassMap.get(rltClassId));
                retList.add(dataModuleRltClassDto);
            }
        }));
        return retList;
    }

    private List<String> getSearchFiles(ESCIClassInfo sourceCiClass) {
        ArrayList<String> sourceSearchFiles = Lists.newArrayList();
        for (ESCIAttrDefInfo attrDef : sourceCiClass.getAttrDefs()) {
            if(attrDef.getIsCiDisp()==1||attrDef.getIsMajor()==1){
                sourceSearchFiles.add(attrDef.getProStdName()) ;
            }
        }
        return sourceSearchFiles;
    }

    @Override
    public List<CcCiClassInfo> queryAllClasses(Long domainId) {
        List<CcCiClassInfo> rltClss = esRltClassSvc.queryAllClasses(domainId);
        if (rltClss != null && rltClss.size() > 0) {
            // 填补分类下关系数量信息
            Set<Long> clsIds = new HashSet<>();
            rltClss.forEach(cls -> clsIds.add(cls.getCiClass().getId()));
            Map<String, Long> countRes = esCiRltSvc.groupByCountField("classId",
                    QueryBuilders.termsQuery("classId", clsIds));
            rltClss.forEach(cls -> {
                Long count = countRes.get(cls.getCiClass().getId().toString());
                count = count == null ? 0L : count;
                cls.setCiCount(count);
            });
        }
        return rltClss;
    }

    public Page<CcCiRltInfo> searchRltByBeanVO(ESRltSearchBeanVO bean) {
        return esCiRltSvc.searchRltByBeanVO(bean);
    }

    public Resource exportCiRltByConditions(ESRltSearchBeanVO reqBean, String ownerCode) {
        if (StringUtils.isNotBlank(ownerCode)) {
            reqBean.setOwnerCode(ownerCode);
        }
        List<ESCIRltInfo> findRltList =  esCiRltSvc.searchRltByBeanByCondition(reqBean);

        if (CollectionUtils.isEmpty(reqBean.getRltClassIds()) ) {
            Assert.isTrue(false, "要导出的关系分类id不能传入空");
        }
        // 导出数据
        if (reqBean.getRltClassIds() != null && !reqBean.getRltClassIds().isEmpty()) {
            // 不再提供策略切换，只维护空间最优策略
            return exportCiRltCoreSpaceStrategyByCondition(new HashSet<>(reqBean.getRltClassIds()), findRltList, ownerCode);
        } else {
            // 导出模板
            return exportCiRltTemplate();
        }
    }

    private Resource exportCiRltCoreSpaceStrategyByCondition(Set<Long> rltClassIds, List<ESCIRltInfo> findRltList, String ownerCode) {
        Resource resource = null;
        // 处理指定关系数据得情况
        Map<Long, List<ESCIRltInfo>> clsIdRltsMap = null;
        // 判断入参合法性顺便取到关系分类信息，后面excel的sheet要用
        BoolQueryBuilder query = new BoolQueryBuilder();
        query.must(QueryBuilders.termsQuery("id", rltClassIds));
        /*if (SysUtil.StringUtil.isNotBack(ownerCode)) {
            query.must(QueryBuilders.termQuery("ownerCode", ownerCode));
        }*/
        List<ESCIClassInfo> rltClassInfos = esRltClassSvc.getListByQuery(query);
        Assert.isTrue(rltClassInfos != null && rltClassInfos.size() == rltClassIds.size(), "关系分类中有失效数据");
        InputStream temIs = null;
        // 待导出sheet页
        Workbook workBook = null;
        temIs = this.getClass().getResourceAsStream("/static_res/rlt_import_new_template.xlsx");
        try {
            workBook = new XSSFWorkbook(temIs);
        } catch (IOException e) {
            Assert.isTrue(false, "读取模板文件异常");
        }
        // 创建关系定义
        Sheet rltDefinition = workBook.getSheet("关系定义");
        int rltDefinitionIndex = 1;
        // 分类名称 属性名称 属性类型 方向 是否Label 是否主键
        for (ESCIClassInfo rltClassInfo : rltClassInfos) {
            List<CcCiAttrDef> attrDefs = rltClassInfo.getCcAttrDefs();
            if (attrDefs == null || attrDefs.isEmpty()) {
                Row row = rltDefinition.createRow(rltDefinitionIndex);
                row.createCell(0).setCellValue(rltClassInfo.getClassName()); // 分类名称
                rltDefinitionIndex++;
            } else {
                for (CcCiAttrDef attrDef : attrDefs) {
                    Row row = rltDefinition.createRow(rltDefinitionIndex);
                    row.createCell(0).setCellValue(rltClassInfo.getClassName()); // 分类名称
                    row.createCell(1).setCellValue(attrDef.getProName()); // 属性名称
                    row.createCell(2).setCellValue(AttrNameKeyEnum.valueOf(attrDef.getProType()).getValue()); // 属性类型
                    row.createCell(3).setCellValue(CIRltSvc.LineLabelAlignEnum.valueOf(attrDef.getLineLabelAlign()).getValue()); // 方向
                    row.createCell(4).setCellValue(attrDef.getIsCiDisp() == 0 ? "否" : "是"); // 是否Label
                    row.createCell(5).setCellValue(attrDef.getIsMajor() == 0 ? "否" : "是"); // 是否主键
                    rltDefinitionIndex++;
                }
            }
        }
        // 遍历关系分类，每一个关系分类都是一个sheet
        for (ESCIClassInfo rltCls : rltClassInfos) {
            System.gc();
            Sheet sheet = workBook.createSheet(rltCls.getClassCode());
            int rowIndex = 0;
            Row titleRow = sheet.createRow(rowIndex);
            // 表头定义-固定9个头加上关系分类的属性std
            List<String> titles = this.buildRltTitleRow(workBook, titleRow, rltCls.getCcAttrDefs());
            FileUtil.ExcelUtil.setAutoColumnSizeByRow(sheet, titleRow);
            wirteRltSheetRow(sheet, titles, rowIndex, findRltList);
        }
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        try {
            workBook.write(os);
            resource = new ByteArrayResource(os.toByteArray(),
                    FileUtil.ExcelUtil.getExportFileName("关系数据", ".xlsx", true));
        } catch (IOException e) {
            log.error("导出ci关系写入输出流异常", e);
        } finally {
            try {
                os.close();
                workBook.close();
            } catch (IOException e) {
                log.error("导出ci关系关闭输出流异常", e);
            }
        }
        return resource;
    }
}